Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
416 views
in Technique[技术] by (71.8m points)

botframework - Bot Framework - Sign-In Card, how get auth result

working with Microsoft Bot Framework V3 I started using Sign-In Cards.

I did a simple cut and paste from example code page into my code and let's say it works (compiles): https://docs.botframework.com/en-us/csharp/builder/sdkreference/attachments.html

What was expected is a behavior similar to oauth process so to be redirected to , do it's own stuffs and return the auth resul including all informations.

What I realized is that it simply open a new web page to the link I provided, that's all...

No other code founded elsewere...

So far it seems useless as I could provide the link simply with normal messages based on this behavior, also there is no communication with the bot.

Did I missed something?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Option 1) Custom Authentication using Windows Active Directory

I have made a custom authentication technique which queries Windows AD using Kerberos LDAP Protocol and using PrincipalContext class.

Firstly, in Root Dialog save the context of the chat in the ConversationReference and encode it using Base64 encoding.

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.ConnectorEx;
using System.Threading;

namespace ADAuthBot.Dialogs
{
    [Serializable]
    public class RootDialog : IDialog<object>
    {
        public async Task StartAsync(IDialogContext context)
        {
            await context.PostAsync("Welcome to Auth Bot!");
            context.Wait(MessageReceivedAsync);
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
            var message = await result as Activity;
            ConversationReference conversationReference = message.ToConversationReference();

            string username = string.Empty;

            context.PrivateConversationData.SetValue<string>("usertext", message.Text);
            if (!context.PrivateConversationData.TryGetValue<string>("Username", out username))
            {
                string encodedCookie = UrlToken.Encode(conversationReference);
                await AuthDialog.createPromptForLogin(context, encodedCookie);
            }

            else
            {
                context.Call(this, ResumeAfter);
            }


        }

        private async Task ResumeAfter(IDialogContext context, IAwaitable<object> result)
        {
            var item = await result;
            context.Wait(MessageReceivedAsync);
        }
    }
}

Next, we come to the Auth Dialog in which we create a Sign-In Card and give the URL page that needs to be opened on the click of the authenticate button.

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.ConnectorEx;
using System.Threading;
using System.Collections.Generic;
using System.Configuration;

namespace ADAuthBot.Dialogs
{
    [Serializable]
    public class AuthDialog: IDialog<object>
    {
        static string authenticationUrl = string.Empty;  //Authentication URL is the MVC View URL, which will have the username and password window.
        static string callbackurl = string.Empty;
        static AuthDialog()
        {
            authenticationUrl = ConfigurationManager.AppSettings["AuthenticationUrl"];
            callbackurl = ConfigurationManager.AppSettings["AuthCallbackUrl"];
        }
        public async Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
        }

        public static async Task createPromptForLogin(IDialogContext context, string encodedCookie)
        {
            IMessageActivity response = context.MakeMessage();
            response.Attachments = new List<Attachment>();

            SigninCard signincard = new SigninCard()
            {
                Text = "Click here to sign in",
                Buttons = new List<CardAction>() {
                        new CardAction()
                        {
                            Title = "Authentication Required",
                            Type = ActionTypes.OpenUrl,
                            Value = $"{authenticationUrl}?{encodedCookie}"
                        }
                    }
            };

            response.Attachments.Add(signincard.ToAttachment());
            await context.PostAsync(response);
        }
    }
}

Next I made a MVC view which inputs your username and password and sends it to the ADAuthController to query it against the Windows Active Directory.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace ADAuthService.Controllers
{
    public class LoginADController : Controller
    {
        // GET: LoginAD
        [Route("Login")]
        public ActionResult LoginUsingAD()
        {
            return View();
        }

    }
}

Next I created a simple Razor view which uses jQuery AJAX call to send username and password by encoding it in base64 encoding by using Javascript's btoa() function.

<script src="~/scripts/jquery-3.2.1.min.js"></script>
<script src="~/scripts/bootstrap.min.js"></script>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />

<script>
        $(function () {
            $("#txtUserName").html("");
            $("#txtPassword").html("");


            function make_base64_auth(username, password) {
                var tok = username + ' ' + password;
                var hash = btoa(tok);
                return hash;
            }

            $("#btnSubmit").click(function () {
                var userName = $("#txtUserName").val();
                var passWord = $("#txtPassword").val();

                var conversationReference = $(location).attr('search');
                console.log(conversationReference);

                var dataToBeSent = {
                    "ConversationReference": conversationReference,
                    "HashedUserCredentials": make_base64_auth(userName, passWord)
                };

                $.ajax({
                    url: "http://localhost:1070/api/Login",
                    method: "POST",
                    dataType: "json",
                    data: dataToBeSent,
                    contentType: "application/json",
                    crossDomain: true,
                    success: function (data) {
                        debugger;
                        console.log(data);
                        if(!$.isEmptyObject(data))
                            alert(data);
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        debugger;
                        if (!$.isEmptyObject(jqXHR))
                        alert("Something happened wrong because: " + jqXHR.responseText);
                    }
                });

            });
        });
</script>
<div class="panel-info">
    <div class="panel panel-heading">
        Enter your credentials
    </div>
    <div class="panel panel-body">
        <div class="form-group">
            <label for="username">Username: </label> <input id="txtUserName" type="text" placeholder="Enter username" required class="form-control" />
            <label for="password">Password: </label> <input id="txtPassword" type="password" placeholder="Enter password" required class="form-control" />
            <button id="btnSubmit" class="btn btn-info">Submit</button>
            <button id="btnReset" class="btn btn-danger" type="reset">Reset</button>
        </div>
    </div>
</div>

I made a model class to store whether a user is identified or not.

namespace ADAuthService.Models
{
    public class AuthenticatedUser
    {
        public string AuthenticatedUserName { get; set; } = string.Empty;
        public bool IsAuthenticated { get; set; } = false;
    }
}

and a model class to get details from MVC View.

namespace ADAuthService.Models
{
    public class UserDetailsHashed
    {
        public string HashedUserCredentials { get; set; } = string.Empty;
        public string ConversationReference { get; set; } = string.Empty;
    }
}

Now the main content is to write a method which queries the Windows Active Directory by taking username, password and domain as input. After authenticating I am using the Service URL to send the authenticated user's name to the bot framework by resolving the scope using Autofac IoC Container.

using ADAuthService.Models;
using Autofac;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Cors;

namespace ADAuthService.Controllers
{
    public class ADAuthController : ApiController
    {
        [NonAction]
        private void extractUserDetailsFromHash(UserDetailsHashed userDetails, out string username, out string password, out string conversationReference)
        {
            try
            {
                string[] userCredentials = userDetails.HashedUserCredentials.Split(' ');
                byte[] userCredentialsBinary = Convert.FromBase64String(userCredentials.Last());
                string decodedString = Encoding.UTF8.GetString(userCredentialsBinary);
                string[] decodedStringArray = decodedString.Split(' ');
                username = decodedStringArray[0];
                password = decodedStringArray[1];
                string[] userConversationReference = userDetails.ConversationReference.Split('?');
                conversationReference = userConversationReference[1];
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        [NonAction]
        private Task<AuthenticatedUser> ValidateUserAgainstAD(string username, string password)
        {
            AuthenticatedUser user = new AuthenticatedUser();
            return Task.Run<AuthenticatedUser>(() => {
                string ADDisplayName = string.Empty;
                try
                {
                    using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, System.Environment.UserDomainName))
                    {
                        bool isValidCredentials = ctx.ValidateCredentials(username, password, ContextOptions.Negotiate);

                        // Additional check to search user in directory.
                        if (isValidCredentials)
                        {
                            UserPrincipal prUsr = new UserPrincipal(ctx);
                            prUsr.SamAccountName = username;
                            PrincipalSearcher srchUser = new PrincipalSearcher(prUsr);
                            UserPrincipal foundUsr = srchUser.FindOne() as UserPrincipal;

                            if (foundUsr != null)
                            {
                                user.AuthenticatedUserName = foundUsr.DisplayName;
                                user.IsAuthenticated = isValidCredentials;
                            }
                        }
                        else
                            throw new AuthenticationException($"Couldn't query no such credentials in Microsoft Active Directory such as Username: {username} and Password: {password}. Try entering a valid username and password combination.");
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                return user;
            });    
        }

        [NonAction]
        public async Task ReplyToBot(string userName, string encodedConversa

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...