|
| 1 | +// Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | +// Licensed under the MIT License. |
| 3 | + |
| 4 | +using Microsoft.Agents.Builder; |
| 5 | +using Microsoft.Agents.Builder.App; |
| 6 | +using Microsoft.Agents.Builder.State; |
| 7 | +using Microsoft.Agents.Builder.UserAuth; |
| 8 | +using Microsoft.Agents.Core.Models; |
| 9 | +using System.Net.Http; |
| 10 | +using System.Net.Http.Headers; |
| 11 | +using System.Text; |
| 12 | +using System.Text.Json.Nodes; |
| 13 | +using System.Threading; |
| 14 | +using System.Threading.Tasks; |
| 15 | + |
| 16 | + |
| 17 | +public class AuthAgent : AgentApplication |
| 18 | +{ |
| 19 | + /// <summary> |
| 20 | + /// Default Sign In Name |
| 21 | + /// </summary> |
| 22 | + private string _defaultDisplayName = "Unknown User"; |
| 23 | + /// <summary> |
| 24 | + /// Authorization Handler Name to use for queries |
| 25 | + /// </summary> |
| 26 | + private string _signInHandlerName = string.Empty; |
| 27 | + |
| 28 | + /// <summary> |
| 29 | + /// Describes the agent registration for the Authorization Agent |
| 30 | + /// This agent will handle the sign-in and sign-out processes for a user. |
| 31 | + /// </summary> |
| 32 | + /// <param name="options">AgentApplication Configuration objects to configure and setup the Agent Application</param> |
| 33 | + public AuthAgent(AgentApplicationOptions options) : base(options) |
| 34 | + { |
| 35 | + /* |
| 36 | + During setup of the Agent Application, Register Event Handlers for the Agent. |
| 37 | + For this example we will register a welcome message for the user when they join the conversation, then configure sign-in and sign-out commands. |
| 38 | + Additionally, we will add events to handle notifications of sign-in success and failure, these notifications will report the local log instead of back to the calling agent. . |
| 39 | +
|
| 40 | + This handler should only register events and setup state as it can be called multiple times before agent events are invoked. |
| 41 | + */ |
| 42 | + |
| 43 | + // this sets the default signin handler to the default handler name. This is used to identify the handler that will be used for the sign-in process later in this code. |
| 44 | + _signInHandlerName = UserAuthorization.DefaultHandlerName; |
| 45 | + |
| 46 | + /* |
| 47 | + When a conversation update event is triggered. |
| 48 | + */ |
| 49 | + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); |
| 50 | + |
| 51 | + ///* |
| 52 | + // Handles the user sending a Login or LogOut command using the specific keywords '-signout' |
| 53 | + //*/ |
| 54 | + OnMessage("/signout", async (turnContext, turnState, cancellationToken) => |
| 55 | + { |
| 56 | + // force a user signout to reset the user state |
| 57 | + // this is needed to reset the token in Azure Bot Services if needed. |
| 58 | + await UserAuthorization.SignOutUserAsync(turnContext, turnState, cancellationToken: cancellationToken); |
| 59 | + await turnContext.SendActivityAsync("You have signed out", cancellationToken: cancellationToken); |
| 60 | + }, rank: RouteRank.Last); |
| 61 | + |
| 62 | + |
| 63 | + /* |
| 64 | + The UserAuthorization Class provides methods and properties to manage and access user authentication tokens |
| 65 | + You can use this class to interact with the authentication process, including signing in and signing out users, accessing tokens, and handling authentication events. |
| 66 | + */ |
| 67 | + |
| 68 | + // Register Events for SignIn and SignOut on the authentication class to track the status of the user, notify the user of the status of their authentication process, or log the status of the authentication process. |
| 69 | + UserAuthorization.OnUserSignInFailure(OnUserSignInFailure); |
| 70 | + |
| 71 | + // Registers a general event handler that will pick up any message activity that is not covered by the previous events handlers. |
| 72 | + OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last); |
| 73 | + } |
| 74 | + |
| 75 | + /// <summary> |
| 76 | + /// This method is called to handle the conversation update event when a new member is added to or removed from the conversation. |
| 77 | + /// </summary> |
| 78 | + /// <param name="turnContext"><see cref="ITurnContext"/></param> |
| 79 | + /// <param name="turnState"><see cref="ITurnState"/></param> |
| 80 | + /// <param name="cancellationToken"><see cref="CancellationToken"/></param> |
| 81 | + private async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) |
| 82 | + { |
| 83 | + /* |
| 84 | + In this example, we will send a welcome message to the user when they join the conversation. |
| 85 | + We do this by iterating over the incoming activity members added to the conversation and checking if the member is not the agent itself. |
| 86 | + Then a greeting notice is provided to each new member of the conversation. |
| 87 | + */ |
| 88 | + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) |
| 89 | + { |
| 90 | + if (member.Id != turnContext.Activity.Recipient.Id) |
| 91 | + { |
| 92 | + string displayName = await GetDisplayName(turnContext); |
| 93 | + StringBuilder sb = new StringBuilder(); |
| 94 | + sb.AppendLine($"Welcome to the AutoSignIn Example, **{displayName}**!."); |
| 95 | + sb.AppendLine("This Agent automatically signs you in when you first connect."); |
| 96 | + sb.AppendLine("You can use the following commands to interact with the agent:"); |
| 97 | + sb.AppendLine("/signout: Sign out of the agent and force it to reset the login flow on next message."); |
| 98 | + if (displayName.Equals(_defaultDisplayName)) |
| 99 | + { |
| 100 | + sb.AppendLine("**WARNING: We were unable to get your display name with the current access token.. please use the /signout command before proceeding**"); |
| 101 | + } |
| 102 | + sb.AppendLine(""); |
| 103 | + sb.AppendLine("Type anything else to see the agent echo back your message."); |
| 104 | + await turnContext.SendActivityAsync(MessageFactory.Text(sb.ToString()), cancellationToken); |
| 105 | + sb.Clear(); |
| 106 | + } |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + /// <summary> |
| 111 | + /// Handles general message loop. |
| 112 | + /// </summary> |
| 113 | + /// <param name="turnContext"><see cref="ITurnContext"/></param> |
| 114 | + /// <param name="turnState"><see cref="ITurnState"/></param> |
| 115 | + /// <param name="cancellationToken"><see cref="CancellationToken"/></param> |
| 116 | + /// <returns></returns> |
| 117 | + private async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) |
| 118 | + { |
| 119 | + /* |
| 120 | + When Auto Sign in is properly configured, the user will be automatically signed in when they first connect to the agent using the default handler chosen in the UserAuthorization configuration. |
| 121 | + IMPORTANT: The ReadMe associated with this sample, instructs you on confiuring the Azure Bot Service Registration with the scopes to allow you to read your own information from Graph. you must have completed that for this sample to work correctly. |
| 122 | +
|
| 123 | + If the sign in process is successful, the user will be signed in and the token will be available from the UserAuthorization.GetTurnToken(_signInHandlerName) function call. |
| 124 | + if the sign in was not successful, you will get a Null when you call the UserAuthorization.GetTurnToken(_signInHandlerName) function. |
| 125 | + */ |
| 126 | + |
| 127 | + // Check for Access Token from the Authorization Sub System. |
| 128 | + if (string.IsNullOrEmpty(UserAuthorization.GetTurnToken(_signInHandlerName))) |
| 129 | + { |
| 130 | + // Failed to get access token here, and we will now bail out of this message loop. |
| 131 | + await turnContext.SendActivityAsync($"The auto sign in process failed and no access token is available", cancellationToken: cancellationToken); |
| 132 | + return; |
| 133 | + } |
| 134 | + |
| 135 | + // We have the access token, now try to get your user name from graph. |
| 136 | + string displayName = await GetDisplayName(turnContext); |
| 137 | + if (displayName.Equals(_defaultDisplayName)) |
| 138 | + { |
| 139 | + // Handle error response from Graph API |
| 140 | + await turnContext.SendActivityAsync($"Failed to get user information from Graph API \nDid you update the scope correctly in Azure bot Service?. If so type in /signout to force signout the current user", cancellationToken: cancellationToken); |
| 141 | + return; |
| 142 | + } |
| 143 | + |
| 144 | + // Now Echo back what was said with your display name. |
| 145 | + await turnContext.SendActivityAsync($"**{displayName} said:** {turnContext.Activity.Text}", cancellationToken: cancellationToken); |
| 146 | + } |
| 147 | + |
| 148 | + /// <summary> |
| 149 | + /// This method is called when the sign-in process fails with an error indicating why . |
| 150 | + /// </summary> |
| 151 | + /// <param name="turnContext"></param> |
| 152 | + /// <param name="turnState"></param> |
| 153 | + /// <param name="handlerName"></param> |
| 154 | + /// <param name="response"></param> |
| 155 | + /// <param name="initiatingActivity"></param> |
| 156 | + /// <param name="cancellationToken"></param> |
| 157 | + /// <returns></returns> |
| 158 | + private async Task OnUserSignInFailure(ITurnContext turnContext, ITurnState turnState, string handlerName, SignInResponse response, IActivity initiatingActivity, CancellationToken cancellationToken) |
| 159 | + { |
| 160 | + // Raise a notification to the user that the sign-in process failed. |
| 161 | + await turnContext.SendActivityAsync($"Manual Sign In: Failed to login to '{handlerName}': {response.Error.Message}", cancellationToken: cancellationToken); |
| 162 | + } |
| 163 | + |
| 164 | + /// <summary> |
| 165 | + /// Gets the display name of the user from the Graph API using the access token. |
| 166 | + /// </summary> |
| 167 | + /// <param name="turnContext"><see cref="ITurnState"/></param> |
| 168 | + /// <returns></returns> |
| 169 | + private async Task<string> GetDisplayName(ITurnContext turnContext) |
| 170 | + { |
| 171 | + string displayName = _defaultDisplayName; |
| 172 | + string accessToken = UserAuthorization.GetTurnToken(_signInHandlerName); |
| 173 | + string graphApiUrl = $"https://graph.microsoft.com/v1.0/me"; |
| 174 | + HttpClient client = new HttpClient(); |
| 175 | + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); |
| 176 | + HttpResponseMessage response = await client.GetAsync(graphApiUrl); |
| 177 | + if (response.IsSuccessStatusCode) |
| 178 | + { |
| 179 | + var content = await response.Content.ReadAsStringAsync(); |
| 180 | + var graphResponse = JsonNode.Parse(content); |
| 181 | + displayName = graphResponse!["displayName"].GetValue<string>(); |
| 182 | + } |
| 183 | + return displayName; |
| 184 | + } |
| 185 | + |
| 186 | + |
| 187 | +} |
0 commit comments