public async Task StartAsync(IDialogContext context) { Logger.Info("StartAsync..."); var incoming = context.Activity as Activity; var jsonText = JsonConvert.SerializeObject(incoming.ChannelData); Logger.Info($"JsonText={jsonText}"); var channelData = JsonConvert.DeserializeObject <OCSBot.Shared.Models.DirectLineChannelData>(jsonText); Logger.Info($"ChannelData.DirectLineBotID={channelData.DirectLineBotID}"); var storage = new AgentStatusStorage(ConfigurationHelper.GetString("BotStatusDBConnectionString")); var localConversation = (await storage.FindMyConversationActivityAsync(channelData.UserID)).FirstOrDefault(); Logger.Info($"localConversation={localConversation}"); var resumptionCookie = UrlToken.Decode <ResumptionCookie>(localConversation.LocalActivity); var localActivity = resumptionCookie.GetMessage(); var message = ((Activity)context.Activity).Text.Replace("reply:", ""); message = $"[{incoming.From.Name}]{message}"; var reply = localActivity.CreateReply(message); Logger.Info($"reply={JsonConvert.SerializeObject(reply)}"); var localConnector = new ConnectorClient(new Uri(localActivity.ServiceUrl), new MicrosoftAppCredentials( ConfigurationHelper.GetString("MicrosoftAppId"), ConfigurationHelper.GetString("MicrosoftAppPassword")), true); Microsoft.Bot.Connector.Conversations localConversations = new Microsoft.Bot.Connector.Conversations(localConnector); localConversations.ReplyToActivity(reply); Logger.Info("Done"); }
public async Task <HttpResponseMessage> Get(string code, string state, CancellationToken cancellationToken) { // 從 state 參數轉換為原本的 ConversationReference ConversationReference conversationReference = UrlToken.Decode <ConversationReference>(state); // 請求拿到 Google OAuth 的 Access Token var accessToken = await GoogleOAuthHelper.ExchangeCodeForGoogleAccessToken(code, BotUtility.OAuthCallbackURL); var msg = conversationReference.GetPostToBotMessage(); // 取得目前談話對象的容器,並且把 UserData 加入 Access Token using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, msg)) { IStateClient sc = scope.Resolve <IStateClient>(); BotData userData = sc.BotState.GetUserData(msg.ChannelId, msg.From.Id); userData.SetProperty(BotUtility.AccessToken, accessToken.AccessToken); sc.BotState.SetUserData(msg.ChannelId, msg.From.Id, userData); } // 設定 ResumeAsync 回到 MessagesController 的識別值 (例如: 使用 token 關鍵字, 真實案例不適合這樣用) msg.Text = "token:" + accessToken.AccessToken; // 要記得使用 RsumeAsync 才能夠接回原本的 converstaion await Conversation.ResumeAsync(conversationReference, msg); return(Request.CreateResponse("ok")); }
public void UrlToken_Can_Serialize_Address() { var expected = MakeAddress(); var encoded = UrlToken.Encode(expected); var actual = UrlToken.Decode <Address>(encoded); Assert.AreEqual(expected, actual); }
public void UrlToken_Can_Serialize_ConversationReference() { // https://github.com/Microsoft/BotBuilder/pull/1279 var expected = MakeCookie(); var encoded = UrlToken.Encode(expected); var actual = UrlToken.Decode <ConversationReference>(encoded); Assert.AreEqual(expected, actual); }
private static CallbackModel GetCallbackModel(string state) { var model = new CallbackModel { Query = GetQuery(state) }; model.ConversationReference = UrlToken.Decode <ConversationReference>(model.Query["conversationRef"]); model.Message = model.ConversationReference.GetPostToBotMessage(); return(model); }
public static async Task <HttpResponseMessage> Resolve(HttpRequestMessage request, int maxWriteAttempts) { NameValueCollection parameters = null; if (request.Method == HttpMethod.Get) { parameters = request.RequestUri.ParseQueryString(); } else if (request.Method == HttpMethod.Post) { parameters = await request.Content.ReadAsFormDataAsync(); } // Create the message that is send to conversation to resume the login flow string state = parameters["state"]; var resumptionCookie = UrlToken.Decode <ResumptionCookie>(state); var message = resumptionCookie.GetMessage(); string dialogId; using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { IStateClient sc = scope.Resolve <IStateClient>(); BotData userData = sc.BotState.GetUserData(message.ChannelId, message.From.Id); dialogId = userData.GetProperty <string>(AuthenticationConstants.AuthHandlerKey); } AuthCallbackHandler handler; switch (dialogId) { case AuthenticationConstants.AuthDialogId_AzureAD: handler = new mStack.API.Bots.AzureAD.AuthCallbackHandler(maxWriteAttempts); break; case AuthenticationConstants.AuthDialogId_ExactOnline: handler = new mStack.API.Bots.ExactOnline.AuthCallbackHandler(maxWriteAttempts); break; default: throw new ArgumentException("Unknown auth handler type."); } return(await handler.ProcessOAuthCallback(parameters)); }
public async Task <HttpResponseMessage> LogOut([FromUri] string conversationRef) { // Get the conversation reference from the URL (this was specified when triggering logout in the first place). Send a message // to the user confirming logout is complete. var conversationRefDecoded = UrlToken.Decode <ConversationReference>(conversationRef); var message = conversationRefDecoded.GetPostToBotMessage(); var client = new ConnectorClient(new Uri(message.ServiceUrl)); var replyMessage = message.CreateReply("You are now logged out."); await client.Conversations.SendToConversationAsync((Activity)replyMessage); // Show a message in the browser indicating logout is complete. var resp = new HttpResponseMessage(HttpStatusCode.OK); resp.Content = new StringContent($"<html><head><script type='text/javascript'>window.close();</script></head><body>You are now logged out of SharePoint. You can close this tab.</body></html>", System.Text.Encoding.UTF8, @"text/html"); return(resp); }
public async Task <string> Get(string cookie, CancellationToken token) { // get the TokenCache stored per-user from within OpenIdConnectOptions.Events.OnAuthorizationCodeReceived var authenticateContext = new AuthenticateContext(CookieAuthenticationDefaults.AuthenticationScheme); await this.HttpContext.Authentication.AuthenticateAsync(authenticateContext); string tokenBase64; if (authenticateContext.Properties.TryGetValue(Keys.TokenCache, out tokenBase64)) { byte[] tokenBlob = Convert.FromBase64String(tokenBase64); // decode the resumption cookie from the url var resume = UrlToken.Decode <ResumptionCookie>(cookie); var continuation = resume.GetMessage(); using (var scope = DialogModule.BeginLifetimeScope(Container.Instance, continuation)) { var botData = scope.Resolve <IBotData>(); await botData.LoadAsync(token); var data = botData.UserData; var tenantID = this.User.FindFirst(Keys.TenantID); var objectIdentifier = this.User.FindFirst(Keys.ObjectID); data.SetValue(Keys.ObjectID, objectIdentifier.Value); data.SetValue(Keys.TenantID, tenantID.Value); data.SetValue(Keys.TokenCache, tokenBlob); await botData.FlushAsync(token); } return("You're now logged-in - continue talking to the bot!"); } else { throw new InvalidOperationException(); } }
public async Task <HttpResponseMessage> Callback([FromUri] string code, [FromUri] string state, CancellationToken cancellationToken) { try { // Use the state parameter to get correct IAuthProvider and ResumptionCookie var decoded = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(state)); var queryString = HttpUtility.ParseQueryString(decoded); var assembly = Assembly.Load(queryString["providerassembly"]); var type = assembly.GetType(queryString["providertype"]); var providername = queryString["providername"]; IAuthProvider authProvider; if (type.GetConstructor(new Type[] { typeof(string) }) != null) { authProvider = (IAuthProvider)Activator.CreateInstance(type, providername); } else { authProvider = (IAuthProvider)Activator.CreateInstance(type); } // Get the conversation reference var conversationRef = UrlToken.Decode <ConversationReference>(queryString["conversationRef"]); Activity message = conversationRef.GetPostToBotMessage(); using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { // Get the UserData from the original conversation IBotDataStore <BotData> stateStore = scope.Resolve <IBotDataStore <BotData> >(); var key = Address.FromActivity(message); var userData = await stateStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None); // Get Access Token using authorization code var authOptions = userData.GetProperty <AuthenticationOptions>($"{authProvider.Name}{ContextConstants.AuthOptions}"); var token = await authProvider.GetTokenByAuthCodeAsync(authOptions, code); // Generate magic number and attempt to write to userdata int magicNumber = GenerateRandomNumber(); bool writeSuccessful = false; uint writeAttempts = 0; while (!writeSuccessful && writeAttempts++ < MaxWriteAttempts) { try { userData.SetProperty($"{authProvider.Name}{ContextConstants.AuthResultKey}", token); if (authOptions.UseMagicNumber) { userData.SetProperty($"{authProvider.Name}{ContextConstants.MagicNumberKey}", magicNumber); userData.SetProperty($"{authProvider.Name}{ContextConstants.MagicNumberValidated}", "false"); } await stateStore.SaveAsync(key, BotStoreType.BotUserData, userData, CancellationToken.None); await stateStore.FlushAsync(key, CancellationToken.None); writeSuccessful = true; } catch (Exception) { writeSuccessful = false; } } var resp = new HttpResponseMessage(HttpStatusCode.OK); if (!writeSuccessful) { message.Text = String.Empty; // fail the login process if we can't write UserData await Conversation.ResumeAsync(conversationRef, message); if (ConfigurationManager.AppSettings.AllKeys.Contains("BotAuth:ErrorHtml")) { resp.Content = new StringContent(ConfigurationManager.AppSettings["BotAuth:ErrorHtml"], System.Text.Encoding.UTF8, @"text/html"); } else { resp.Content = new StringContent("<html><body>Could not log you in at this time, please try again later</body></html>", System.Text.Encoding.UTF8, @"text/html"); } } else { await Conversation.ResumeAsync(conversationRef, message); if (ConfigurationManager.AppSettings.AllKeys.Contains("BotAuth:SucceedHtml")) { resp.Content = new StringContent(string.Format(ConfigurationManager.AppSettings["BotAuth:SucceedHtml"], magicNumber), System.Text.Encoding.UTF8, @"text/html"); } else { resp.Content = new StringContent($"<html><body>Almost done! Please copy this number and paste it back to your chat so your authentication can complete:<br/> <h1>{magicNumber}</h1>.</body></html>", System.Text.Encoding.UTF8, @"text/html"); } } return(resp); } } catch (Exception ex) { // Callback is called with no pending message as a result the login flow cannot be resumed. return(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex)); } }
public static async Task <object> HandleOAuthCallback(HttpRequestMessage req, uint maxWriteAttempts) { try { var queryParams = req.RequestUri.ParseQueryString(); if (req.Method != HttpMethod.Post) { throw new ArgumentException("The OAuth postback handler only supports POST requests."); } var formData = await req.Content.ReadAsFormDataAsync(); string stateStr = formData["state"]; string code = formData["code"]; var resumptionCookie = UrlToken.Decode <ResumptionCookie>(stateStr); var message = resumptionCookie.GetMessage(); using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { AuthenticationSettings authSettings = AuthenticationSettings.GetFromAppSettings(); var client = scope.Resolve <IConnectorClient>(); AuthenticationResult authenticationResult = await AzureActiveDirectoryHelper.GetTokenByAuthCodeAsync(code, authSettings); IStateClient sc = scope.Resolve <IStateClient>(); //IMPORTANT: DO NOT REMOVE THE MAGIC NUMBER CHECK THAT WE DO HERE. THIS IS AN ABSOLUTE SECURITY REQUIREMENT //REMOVING THIS WILL REMOVE YOUR BOT AND YOUR USERS TO SECURITY VULNERABILITIES. //MAKE SURE YOU UNDERSTAND THE ATTACK VECTORS AND WHY THIS IS IN PLACE. int magicNumber = GenerateRandomNumber(); bool writeSuccessful = false; uint writeAttempts = 0; while (!writeSuccessful && writeAttempts++ < maxWriteAttempts) { try { BotData userData = sc.BotState.GetUserData(message.ChannelId, message.From.Id); userData.SetProperty(AuthenticationConstants.AuthResultKey, authenticationResult); userData.SetProperty(AuthenticationConstants.MagicNumberKey, magicNumber); userData.SetProperty(AuthenticationConstants.MagicNumberValidated, "false"); sc.BotState.SetUserData(message.ChannelId, message.From.Id, userData); writeSuccessful = true; } catch (HttpOperationException) { writeSuccessful = false; } } var resp = new HttpResponseMessage(HttpStatusCode.OK); if (!writeSuccessful) { message.Text = String.Empty; // fail the login process if we can't write UserData await Conversation.ResumeAsync(resumptionCookie, message); resp.Content = new StringContent("<html><body>Could not log you in at this time, please try again later</body></html>", System.Text.Encoding.UTF8, @"text/html"); } else { await Conversation.ResumeAsync(resumptionCookie, message); resp.Content = new StringContent($"<html><body>Almost done! Please copy this number and paste it back to your chat so your authentication can complete:<br/> <h1>{magicNumber}</h1>.</body></html>", System.Text.Encoding.UTF8, @"text/html"); } return(resp); } } catch (Exception ex) { // Callback is called with no pending message as a result the login flow cannot be resumed. return(req.CreateErrorResponse(HttpStatusCode.BadRequest, ex)); } }
public async Task <HttpResponseMessage> OAuthCallback( [FromUri] string code, [FromUri] string state, CancellationToken cancellationToken) { try { var queryParams = state; object tokenCache = null; if (string.Equals(AuthSettings.Mode, "v1", StringComparison.OrdinalIgnoreCase)) { tokenCache = new Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache(); } else if (string.Equals(AuthSettings.Mode, "v2", StringComparison.OrdinalIgnoreCase)) { tokenCache = new Microsoft.Identity.Client.TokenCache(); } else if (string.Equals(AuthSettings.Mode, "b2c", StringComparison.OrdinalIgnoreCase)) { } var resumptionCookie = UrlToken.Decode <ResumptionCookie>(queryParams); // Create the message that is send to conversation to resume the login flow var message = resumptionCookie.GetMessage(); using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { var client = scope.Resolve <IConnectorClient>(); AuthResult authResult = null; if (string.Equals(AuthSettings.Mode, "v1", StringComparison.OrdinalIgnoreCase)) { // Exchange the Auth code with Access token var token = await AzureActiveDirectoryHelper.GetTokenByAuthCodeAsync(code, (Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache) tokenCache); authResult = token; } else if (string.Equals(AuthSettings.Mode, "v2", StringComparison.OrdinalIgnoreCase)) { // Exchange the Auth code with Access token var token = await AzureActiveDirectoryHelper.GetTokenByAuthCodeAsync(code, (Microsoft.Identity.Client.TokenCache) tokenCache, Models.AuthSettings.Scopes); authResult = token; } else if (string.Equals(AuthSettings.Mode, "b2c", StringComparison.OrdinalIgnoreCase)) { } IStateClient sc = scope.Resolve <IStateClient>(); //IMPORTANT: DO NOT REMOVE THE MAGIC NUMBER CHECK THAT WE DO HERE. THIS IS AN ABSOLUTE SECURITY REQUIREMENT //REMOVING THIS WILL REMOVE YOUR BOT AND YOUR USERS TO SECURITY VULNERABILITIES. //MAKE SURE YOU UNDERSTAND THE ATTACK VECTORS AND WHY THIS IS IN PLACE. int magicNumber = GenerateRandomNumber(); bool writeSuccessful = false; uint writeAttempts = 0; while (!writeSuccessful && writeAttempts++ < MaxWriteAttempts) { try { BotData userData = sc.BotState.GetUserData(message.ChannelId, message.From.Id); userData.SetProperty(ContextConstants.AuthResultKey, authResult); userData.SetProperty(ContextConstants.MagicNumberKey, magicNumber); userData.SetProperty(ContextConstants.MagicNumberValidated, "false"); sc.BotState.SetUserData(message.ChannelId, message.From.Id, userData); writeSuccessful = true; } catch (HttpOperationException) { writeSuccessful = false; } } var resp = new HttpResponseMessage(HttpStatusCode.OK); if (!writeSuccessful) { message.Text = String.Empty; // fail the login process if we can't write UserData await Conversation.ResumeAsync(resumptionCookie, message); resp.Content = new StringContent("<html><body>Could not log you in at this time, please try again later</body></html>", System.Text.Encoding.UTF8, @"text/html"); } else { await Conversation.ResumeAsync(resumptionCookie, message); resp.Content = new StringContent($"<html><body>Almost done! Please copy this number and paste it back to your chat so your authentication can complete:<br/> <h1>{magicNumber}</h1>.</body></html>", System.Text.Encoding.UTF8, @"text/html"); } return(resp); } } catch (Exception ex) { // Callback is called with no pending message as a result the login flow cannot be resumed. return(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex)); } }
public async Task DispatchAsync(IDialogContext context, IAwaitable <Microsoft.Bot.Connector.IMessageActivity> item) { Microsoft.Bot.Connector.Activity activity = (Microsoft.Bot.Connector.Activity) await item; Logger.Info($"message received from {activity.From.Name}/{activity.From.Id} : {JsonConvert.SerializeObject(activity)}"); Logger.Info($"message received to {activity.Recipient.Name}/{activity.Recipient.Id}"); var storage = new AgentStatusStorage( ConfigurationHelper.GetString("BotStatusDBConnectionString")); if (activity.From.Name.EndsWith("@ocsuser")) { //Messages from OCS User, when message from OCS User sent to this method, it has to be coming from DirectLine AgentStatus agent = null; //retrieve ChannelData which includes channelId for our conversation //TODO:figure out a way that simplier JObject o = (JObject)activity.ChannelData; var os = JsonConvert.SerializeObject(o); DirectLineChannelData channelData = JsonConvert.DeserializeObject <DirectLineChannelData>(os); Logger.Info($"ChannelData = {channelData}"); //ConversationStatus conversation = null; Logger.Info($"RoundTrip = {channelData.RoundTrip}"); //first message send to agent, find an available agent //TODO:the agent has been selected in OCS Bot, need to make it sync // Instead of selecting another one here... agent = (await storage.FindAvailableAgentsAsync()).FirstOrDefault(); var convRecord = (await storage.FindConversationActivityAsync(a => a.UserID == agent.Id)).FirstOrDefault(); convRecord.RemoteConversationID = channelData.ConversationId; convRecord.RemoteBotId = activity.From.Id;//remote user id actually... convRecord.RemoteActivity = UrlToken.Encode <ResumptionCookie>( new ResumptionCookie((Microsoft.Bot.Connector.Activity)activity)); convRecord.RemoteUserId = channelData.UserID; convRecord.RemoteUserName = channelData.UserName; await storage.UpdateConversationActivityAsync(convRecord); Logger.Info($"agent:{agent}"); if (!agent.IsLoggedIn) { //Agent somehow is occupied (logout?) Logger.Info("Agent is occupied"); var reply = activity.CreateReply($"Agent is occupied"); ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl)); await connector.Conversations.ReplyToActivityAsync(reply); } else { //Agnet is available to answer questions, send message to agent Logger.Info("Sending to conversation..."); //TODO:Need to check if current agent is this user //agent.IsOccupied = true; //await storage.UpdateAgentStatusAsync(agent); //First retrieve last conversaion if exists //resumptionCookie = UrlToken.Decode<ResumptionCookie>(conversation.AgentResumptionCookie); var localResumptionCookie = UrlToken.Decode <ResumptionCookie>(convRecord.LocalActivity); Logger.Info($"AgentBot::LocalResumptionCookie:{localResumptionCookie}"); var localActivity = localResumptionCookie.GetMessage(); var localReply = localActivity.CreateReply($"[{activity.From.Name}]{activity.Text}"); var localConnector = new ConnectorClient(new Uri(localActivity.ServiceUrl), new MicrosoftAppCredentials( ConfigurationHelper.GetString("MicrosoftAppId"), ConfigurationHelper.GetString("MicrosoftAppPassword")), true); Microsoft.Bot.Connector.Conversations localConversation = new Microsoft.Bot.Connector.Conversations(localConnector); localConversation.ReplyToActivity(localReply); Logger.Info("done"); return; } } else { resumptionCookie = new ResumptionCookie(await item); await MessageReceivedAsync(context, item); } }
public async Task <ActionResult> Login(LoginViewModel model, string returnUrl) { if (!ModelState.IsValid) { return(View(model)); } #if true //Send message to Agent string resumption = Request.UrlReferrer.Query.Split('=')[1]; ResumptionCookie resumptionCookie = null; //string resumption = Request.QueryString["cookie"]; resumptionCookie = UrlToken.Decode <ResumptionCookie>(resumption); var appId = Configuration.ConfigurationHelper.GetString("MicrosoftAgentBotAppId"); var appPassword = Configuration.ConfigurationHelper.GetString("MicrosoftAgentBotAppPassword"); var agentBotCredentail = new MicrosoftAppCredentials( appId: appId, password: appPassword); Activity activity = (Activity)resumptionCookie.GetMessage(); var userId = activity.From.Id; //Michael - demo - do not check id/password FormsAuthentication.SetAuthCookie(userId, true); StateClient sc = activity.GetStateClient(agentBotCredentail); BotData data = await sc.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id); data.SetProperty("Agent:Authenticated", true); data.SetProperty("Agent:IsAgent", true); await sc.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, data); // Let the user know back in the conversation //var client = new ConnectorClient(new Uri(activity.ServiceUrl), // "34ee43c1-8b3c-467d-a719-e1c88694b390", // "2Pg2M0gfS6uhgUHRX1wZYi9"); ////var client = new ConnectorClient(new Uri(activity.ServiceUrl)); //Activity replyActivity = activity.CreateReply("You've been succesfully authenticated!"); //await client.Conversations.ReplyToActivityAsync(replyActivity); //Activity replyActivity = activity.CreateReply("You've been succesfully authenticated!"); OCSBot.Shared.AgentStatusStorage storage = new OCSBot.Shared.AgentStatusStorage( Configuration.ConfigurationHelper.GetString("BotStatusDBConnectionString")); await storage.UpdateConversationActivityAsync(new OCSBot.Shared.Models.ConversationRecord { UserID = userId, LocalActivity = resumption, LocalChannelID = activity.ChannelId, LocalConversationID = activity.Conversation.Id, LocalBotId = activity.Recipient.Id, LocalUserName = activity.From.Name }); //await storage.UpdateConversationStatusAsync(new OCSBot.Shared.Models.ConversationStatus //{ // AgentID = userId, // ConversationId = activity.Conversation.Id, // AgentResumptionCookie = resumption //}); await storage.UpdateAgentStatusAsync(new OCSBot.Shared.Models.AgentStatus() { Id = userId, IsLoggedIn = true, Office = "POST", Name = "Michael SH Chi", IsOccupied = false, LastConversationEndTime = null, LastConversationStartTime = null, LoginTime = DateTime.UtcNow, ConversationId = activity.Conversation.Id, AgentIdInChannel = activity.From.Id, AgentNameInChannel = activity.From.Name, ChannelId = activity.ChannelId, BotIdInChannel = activity.Recipient.Id, BotNameInChannel = activity.Recipient.Name, ServiceURL = activity.ServiceUrl }); //Conversation.ResumeAsync(resumptionCookie, activity).Wait(5000, System.Threading.CancellationToken.None); //replyActivity.From = new ChannelAccount(activity.Recipient.Id,activity.Recipient.Name); //replyActivity.Recipient = new ChannelAccount(activity.From.Id, activity.From.Name); //client.Conversations.ReplyToActivityAsync(replyActivity.Conversation.Id, activity.Id, replyActivity).Wait(5000, System.Threading.CancellationToken.None); return(RedirectToLocal("/authenticated.html")); #endif // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, change to shouldLockout: true var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout : false); switch (result) { case SignInStatus.Success: //TODO:send message to Bot #if false //Send message to Agent string resumption = Request.UrlReferrer.Query.Split('=')[1]; ResumptionCookie resumptionCookie = null; //string resumption = Request.QueryString["cookie"]; resumptionCookie = UrlToken.Decode <ResumptionCookie>(resumption); var agentBotCredentail = new MicrosoftAppCredentials( appId: "a6bae9f3-0817-443b-9247-ffdd02dabccd", password: "******"); Activity activity = (Activity)resumptionCookie.GetMessage(); StateClient sc = activity.GetStateClient(agentBotCredentail); BotData data = await sc.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id); data.SetProperty("Authenticated", true); await sc.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, data); // Let the user know back in the conversation var client = new ConnectorClient(new Uri(activity.ServiceUrl), "a6bae9f3-0817-443b-9247-ffdd02dabccd", "bsi2LBN820jcYRfgYxXqtnN"); Activity replyActivity = activity.CreateReply("You've been succesfully authenticated!"); await client.Conversations.ReplyToActivityAsync(replyActivity.Conversation.Id, activity.Id, replyActivity); #endif return(RedirectToLocal(returnUrl)); case SignInStatus.LockedOut: return(View("Lockout")); case SignInStatus.RequiresVerification: return(RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe })); case SignInStatus.Failure: default: ModelState.AddModelError("", "Invalid login attempt."); return(View(model)); } }
public async Task <HttpResponseMessage> ProcessOAuthCallback(NameValueCollection parameters) { try { var queryParams = parameters["state"]; var resumptionCookie = UrlToken.Decode <ResumptionCookie>(queryParams); // Create the message that is send to conversation to resume the login flow var message = resumptionCookie.GetMessage(); using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { var client = scope.Resolve <IConnectorClient>(); AuthenticationResult authResult = null; // Exchange the Auth code with Access token authResult = await GetTokenByAuthCodeAsync(parameters); IStateClient sc = scope.Resolve <IStateClient>(); //IMPORTANT: DO NOT REMOVE THE MAGIC NUMBER CHECK THAT WE DO HERE. THIS IS AN ABSOLUTE SECURITY REQUIREMENT //REMOVING THIS WILL REMOVE YOUR BOT AND YOUR USERS TO SECURITY VULNERABILITIES. //MAKE SURE YOU UNDERSTAND THE ATTACK VECTORS AND WHY THIS IS IN PLACE. int magicNumber = GenerateRandomNumber(); bool writeSuccessful = false; uint writeAttempts = 0; while (!writeSuccessful && writeAttempts++ < _maxWriteAttempts) { try { BotData userData = sc.BotState.GetUserData(message.ChannelId, message.From.Id); userData.SetProperty(dialogId + '_' + AuthenticationConstants.AuthResultKey, authResult); userData.SetProperty(dialogId + '_' + AuthenticationConstants.MagicNumberKey, magicNumber); userData.SetProperty(dialogId + '_' + AuthenticationConstants.MagicNumberValidated, "false"); sc.BotState.SetUserData(message.ChannelId, message.From.Id, userData); writeSuccessful = true; } catch (HttpOperationException) { writeSuccessful = false; } } var resp = new HttpResponseMessage(HttpStatusCode.OK); if (!writeSuccessful) { message.Text = String.Empty; // fail the login process if we can't write UserData await Conversation.ResumeAsync(resumptionCookie, message); resp.Content = new StringContent("<html><body>Could not log you in at this time, please try again later</body></html>", System.Text.Encoding.UTF8, @"text/html"); } else { await Conversation.ResumeAsync(resumptionCookie, message); resp.Content = new StringContent($"<html><body>Almost done! Please copy this number and paste it back to your chat so your authentication can complete:<br/> <h1>{magicNumber}</h1>.</body></html>", System.Text.Encoding.UTF8, @"text/html"); } return(resp); } } catch (Exception ex) { // Callback is called with no pending message as a result the login flow cannot be resumed. var resp = new HttpResponseMessage(HttpStatusCode.InternalServerError); return(resp); } }
public static async Task <HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequestMessage req, TraceWriter log) { log.Info("Callback was triggered!"); using (BotService.Initialize()) { ConfigureStateStore(); GetQueryParams(req, out var code, out var state); try { // Use the state parameter to get correct IAuthProvider and ResumptionCookie var decoded = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(state) ?? throw new InvalidOperationException()); var queryString = HttpUtility.ParseQueryString(decoded); var assembly = Assembly.Load(queryString["providerassembly"]); var type = assembly.GetType(queryString["providertype"]); var providername = queryString["providername"]; IAuthProvider authProvider; if (type.GetConstructor(new[] { typeof(string) }) != null) { authProvider = (IAuthProvider)Activator.CreateInstance(type, providername); } else { authProvider = (IAuthProvider)Activator.CreateInstance(type); } // Get the conversation reference var conversationRef = UrlToken.Decode <ConversationReference>(queryString["conversationRef"]); var message = conversationRef.GetPostToBotMessage(); var magicNumber = GenerateRandomNumber(); var writeSuccessful = false; uint writeAttempts = 0; using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { // Get the UserData from the original conversation var stateStore = scope.Resolve <IBotDataStore <BotData> >(); var key = Address.FromActivity(message); var userData = await stateStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None); // Get Access Token using authorization code var authOptions = userData.GetProperty <AuthenticationOptions>($"{authProvider.Name}{ContextConstants.AuthOptions}"); var token = await authProvider.GetTokenByAuthCodeAsync(authOptions, code); // Generate magic number and attempt to write to userdata while (!writeSuccessful && writeAttempts++ < MaxWriteAttempts) { try { userData.SetProperty($"{authProvider.Name}{ContextConstants.AuthResultKey}", token); if (authOptions.UseMagicNumber) { userData.SetProperty($"{authProvider.Name}{ContextConstants.MagicNumberKey}", magicNumber); userData.SetProperty($"{authProvider.Name}{ContextConstants.MagicNumberValidated}", "false"); } await stateStore.SaveAsync(key, BotStoreType.BotUserData, userData, CancellationToken.None); await stateStore.FlushAsync(key, CancellationToken.None); writeSuccessful = true; } catch (Exception ex) { log.Error(ex.Message); writeSuccessful = false; } } var resp = new HttpResponseMessage(HttpStatusCode.OK); if (!writeSuccessful) { message.Text = string.Empty; // fail the login process if we can't write UserData await Conversation.ResumeAsync(conversationRef, message); resp.Content = new StringContent("<html><body>Could not log you in at this time, please try again later</body></html>", Encoding.UTF8, @"text/html"); return(resp); } await Conversation.ResumeAsync(conversationRef, message); // check if the user has configured an alternate magic number view if (!String.IsNullOrEmpty(authOptions.MagicNumberView)) { var redirect = req.CreateResponse(HttpStatusCode.Moved); redirect.Headers.Location = new Uri(String.Format(authOptions.MagicNumberView, magicNumber), UriKind.Relative); return(redirect); } resp.Content = new StringContent($"<html><body>Almost done! Please copy this number and paste it back to your chat so your authentication can complete:<br/> <h1>{magicNumber}</h1>.</body></html>", Encoding.UTF8, @"text/html"); return(resp); } } catch (Exception ex) { // Callback is called with no pending message as a result the login flow cannot be resumed. return(req.CreateErrorResponse(HttpStatusCode.BadRequest, ex)); } } }
public async Task <HttpResponseMessage> OAuthCallback([FromUri] string code, [FromUri] string state, CancellationToken cancellationToken) { try { object tokenCache = null; if (string.Equals(AuthSettings.Mode, "v1", StringComparison.OrdinalIgnoreCase)) { tokenCache = new Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache(); } else if (string.Equals(AuthSettings.Mode, "v2", StringComparison.OrdinalIgnoreCase)) { tokenCache = new Microsoft.Identity.Client.TokenCache(); } else if (string.Equals(AuthSettings.Mode, "b2c", StringComparison.OrdinalIgnoreCase)) { } // Get the resumption cookie var resumptionCookie = UrlToken.Decode <ResumptionCookie>(state); // Create the message that is send to conversation to resume the login flow var message = resumptionCookie.GetMessage(); using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { var client = scope.Resolve <IConnectorClient>(); AuthResult authResult = null; if (string.Equals(AuthSettings.Mode, "v1", StringComparison.OrdinalIgnoreCase)) { // Exchange the Auth code with Access token var token = await AzureActiveDirectoryHelper.GetTokenByAuthCodeAsync(code, (Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache) tokenCache); authResult = token; } else if (string.Equals(AuthSettings.Mode, "v2", StringComparison.OrdinalIgnoreCase)) { // Exchange the Auth code with Access token var token = await AzureActiveDirectoryHelper.GetTokenByAuthCodeAsync(code, (Microsoft.Identity.Client.TokenCache) tokenCache, Models.AuthSettings.Scopes); authResult = token; } else if (string.Equals(AuthSettings.Mode, "b2c", StringComparison.OrdinalIgnoreCase)) { } IStateClient sc = scope.Resolve <IStateClient>(); //IMPORTANT: DO NOT REMOVE THE MAGIC NUMBER CHECK THAT WE DO HERE. THIS IS AN ABSOLUTE SECURITY REQUIREMENT //REMOVING THIS WILL REMOVE YOUR BOT AND YOUR USERS TO SECURITY VULNERABILITIES. //MAKE SURE YOU UNDERSTAND THE ATTACK VECTORS AND WHY THIS IS IN PLACE. var dataBag = scope.Resolve <IBotData>(); await dataBag.LoadAsync(cancellationToken); int magicNumber = GenerateRandomNumber(); dataBag.UserData.SetValue(ContextConstants.AuthResultKey, authResult); dataBag.UserData.SetValue(ContextConstants.MagicNumberKey, magicNumber); dataBag.UserData.SetValue(ContextConstants.MagicNumberValidated, "false"); await dataBag.FlushAsync(cancellationToken); await Conversation.ResumeAsync(resumptionCookie, message); var resp = new HttpResponseMessage(HttpStatusCode.OK); resp.Content = new StringContent($"<html><body>Almost done! Please copy this number and paste it back to your chat so your authentication can complete: {magicNumber}.</body></html>", System.Text.Encoding.UTF8, @"text/html"); return(resp); } } catch (Exception ex) { // Callback is called with no pending message as a result the login flow cannot be resumed. return(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex)); } }
public async Task <HttpResponseMessage> Callback([FromUri] string code, [FromUri] string state, CancellationToken cancellationToken) { try { // Use the state parameter to get correct IAuthProvider and ResumptionCookie var decoded = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(state)); var queryString = HttpUtility.ParseQueryString(decoded); var assembly = Assembly.Load(queryString["providerassembly"]); var type = assembly.GetType(queryString["providertype"]); var providername = queryString["providername"]; IAuthProvider authProvider; if (type.GetConstructor(new Type[] { typeof(string) }) != null) { authProvider = (IAuthProvider)Activator.CreateInstance(type, providername); } else { authProvider = (IAuthProvider)Activator.CreateInstance(type); } // Get the conversation reference var conversationRef = UrlToken.Decode <ConversationReference>(queryString["conversationRef"]); Activity message = conversationRef.GetPostToBotMessage(); using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { // Get the UserData from the original conversation IStateClient sc = scope.Resolve <IStateClient>(); BotData userData = await sc.BotState.GetUserDataAsync(message.ChannelId, message.From.Id); // Get Access Token using authorization code var authOptions = userData.GetProperty <AuthenticationOptions>($"{authProvider.Name}{ContextConstants.AuthOptions}"); var token = await authProvider.GetTokenByAuthCodeAsync(authOptions, code); // Generate magic number and attempt to write to userdata int magicNumber = GenerateRandomNumber(); bool writeSuccessful = false; uint writeAttempts = 0; while (!writeSuccessful && writeAttempts++ < MaxWriteAttempts) { try { userData.SetProperty($"{authProvider.Name}{ContextConstants.AuthResultKey}", token); if (authOptions.UseMagicNumber) { userData.SetProperty($"{authProvider.Name}{ContextConstants.MagicNumberKey}", magicNumber); userData.SetProperty($"{authProvider.Name}{ContextConstants.MagicNumberValidated}", "false"); } sc.BotState.SetUserData(message.ChannelId, message.From.Id, userData); writeSuccessful = true; } catch (HttpOperationException) { writeSuccessful = false; } } var resp = new HttpResponseMessage(HttpStatusCode.OK); if (!writeSuccessful) { message.Text = String.Empty; // fail the login process if we can't write UserData await Conversation.ResumeAsync(conversationRef, message); resp.Content = new StringContent("<html><body>Could not log you in at this time, please try again later</body></html>", System.Text.Encoding.UTF8, @"text/html"); } else { await Conversation.ResumeAsync(conversationRef, message); resp.Content = new StringContent($"<html><body>Thank you for authenticating. You can now return back to the bot.</body></html>", System.Text.Encoding.UTF8, @"text/html"); } return(resp); } } catch (Exception ex) { // Callback is called with no pending message as a result the login flow cannot be resumed. return(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex)); } }
public async Task <HttpResponseMessage> OAuthCallbackAsync([FromUri] string code, [FromUri] string state, CancellationToken cancellationToken) { Activity message; Address address; ConversationReference conversationReference; CustomerPrincipal principal = null; DateTime startTime; Dictionary <string, double> eventMeasurements; Dictionary <string, string> eventProperties; Dictionary <string, string> stateData; IBotData botData; HttpResponseMessage response; code.AssertNotEmpty(nameof(code)); state.AssertNotEmpty(nameof(state)); try { startTime = DateTime.Now; stateData = UrlToken.Decode <Dictionary <string, string> >(state); address = new Address( stateData[BotConstants.BotIdKey], stateData[BotConstants.ChannelIdKey], stateData[BotConstants.UserIdKey], stateData[BotConstants.ConversationIdKey], stateData[BotConstants.ServiceUrlKey]); conversationReference = address.ToConversationReference(); message = conversationReference.GetPostToBotMessage(); using (ILifetimeScope scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { botData = scope.Resolve <IBotData>(); await botData.LoadAsync(cancellationToken).ConfigureAwait(false); if (!Validate(botData, stateData)) { return(Request.CreateErrorResponse( HttpStatusCode.BadRequest, new InvalidOperationException(Resources.InvalidAuthenticationException))); } principal = await GetCustomerPrincipalAsync( new Uri($"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}:{Request.RequestUri.Port}/{BotConstants.CallbackPath}"), code).ConfigureAwait(false); if (principal == null) { message.Text = Resources.NoRelationshipException; await Conversation.ResumeAsync(conversationReference, message, cancellationToken).ConfigureAwait(false); return(Request.CreateErrorResponse( HttpStatusCode.BadRequest, new InvalidOperationException(Resources.NoRelationshipException))); } botData.PrivateConversationData.SetValue(BotConstants.CustomerPrincipalKey, principal); await botData.FlushAsync(cancellationToken).ConfigureAwait(false); await Conversation.ResumeAsync(conversationReference, message, cancellationToken).ConfigureAwait(false); // Capture the request for the customer summary for analysis. eventProperties = new Dictionary <string, string> { { "CustomerId", principal.CustomerId }, { "Name", principal.Name }, { "ObjectId", principal.ObjectId } }; // Track the event measurements for analysis. eventMeasurements = new Dictionary <string, double> { { "ElapsedMilliseconds", DateTime.Now.Subtract(startTime).TotalMilliseconds }, { "NumberOfIntents", principal.AvailableIntents.Count }, { "NumberOfRoles", principal.Roles.Count } }; Provider.Telemetry.TrackEvent("api/OAuthCallback", eventProperties, eventMeasurements); response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(Resources.SuccessfulAuthentication); } } catch (Exception ex) { Provider.Telemetry.TrackException(ex); response = Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex); } finally { address = null; botData = null; conversationReference = null; eventMeasurements = null; eventProperties = null; message = null; principal = null; } return(response); }
public async Task <HttpResponseMessage> OAuthCallback([FromUri] string code, [FromUri] string state) { try { object tokenCache = null; if (string.Equals(AuthSettings.Mode, "v1", StringComparison.OrdinalIgnoreCase)) { tokenCache = new Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache(); } else if (string.Equals(AuthSettings.Mode, "v2", StringComparison.OrdinalIgnoreCase)) { tokenCache = new Microsoft.Identity.Client.TokenCache(); } else if (string.Equals(AuthSettings.Mode, "b2c", StringComparison.OrdinalIgnoreCase)) { } // Get the resumption cookie var resumptionCookie = UrlToken.Decode <ResumptionCookie>(state); // Create the message that is send to conversation to resume the login flow var message = resumptionCookie.GetMessage(); using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message)) { var client = scope.Resolve <IConnectorClient>(); AuthResult authResult = null; if (string.Equals(AuthSettings.Mode, "v1", StringComparison.OrdinalIgnoreCase)) { // Exchange the Auth code with Access token var token = await AzureActiveDirectoryHelper.GetTokenByAuthCodeAsync(code, (Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache) tokenCache); authResult = token; } else if (string.Equals(AuthSettings.Mode, "v2", StringComparison.OrdinalIgnoreCase)) { //TODO: Scopes definition here // Exchange the Auth code with Access token var token = await AzureActiveDirectoryHelper.GetTokenByAuthCodeAsync(code, (Microsoft.Identity.Client.TokenCache) tokenCache, new string[] { "User.Read" }); authResult = token; } else if (string.Equals(AuthSettings.Mode, "b2c", StringComparison.OrdinalIgnoreCase)) { } var reply = await Conversation.ResumeAsync(resumptionCookie, message); var data = await client.Bots.GetPerUserConversationDataAsync(resumptionCookie.BotId, resumptionCookie.ConversationId, resumptionCookie.UserId); reply.SetBotUserData(ContextConstants.AuthResultKey, authResult); int magicNumber = GenerateRandomNumber(); reply.SetBotUserData(ContextConstants.MagicNumberKey, magicNumber); reply.SetBotUserData(ContextConstants.MagicNumberValidated, "false"); //data.SetProperty(ContextConstants.AuthResultKey, authResult); //data.SetProperty(ContextConstants.MagicNumberKey, magicNumber); //data.SetProperty(ContextConstants.MagicNumberValidated, "false"); //await client.Bots.SetUserDataAsync(resumptionCookie.BotId, resumptionCookie.UserId, data); reply.To = message.From; reply.From = message.To; await client.Messages.SendMessageAsync(reply); var resp = new HttpResponseMessage(HttpStatusCode.OK); resp.Content = new StringContent($"<html><body>Almost done! Please copy this number and paste it back to your chat so your authentication can complete: {magicNumber}.</body></html>", System.Text.Encoding.UTF8, @"text/html"); return(resp); } } catch (Exception ex) { // Callback is called with no pending message as a result the login flow cannot be resumed. return(Request.CreateErrorResponse(HttpStatusCode.BadRequest, new InvalidOperationException("Cannot resume!"))); } }