/// <summary> /// Uses a magic code in message text to create a TokenResponse. /// </summary> /// <param name="userTokenClient">The UserTokenClient to use.</param> /// <param name="connectionName">The Connection Name to use.</param> /// <param name="activity">A message activity.</param> /// <param name="cancellationToken">A CancellationToken.</param> /// <returns>A TokenResponse.</returns> public static async Task <TokenResponse> CreateTokenResponseFromMessageAsync(UserTokenClient userTokenClient, string connectionName, Activity activity, CancellationToken cancellationToken) { // Attempt to recognize a magic code in the message text. var magicCode = RecognizeMagicCode(activity); // If we have a magic code then call the user token service. var userId = activity.From.Id; var channelId = activity.ChannelId; return(magicCode != null ? await userTokenClient.GetUserTokenAsync(userId, connectionName, channelId, magicCode, cancellationToken).ConfigureAwait(false) : null); }
private static async Task <TokenResponse> OnContinueVerifyStateAsync(UserTokenClient userTokenClient, string connectionName, ITurnContext turnContext, CancellationToken cancellationToken) { // Getting the token follows a different flow in Teams. At the signin completion, Teams // will send the bot an "invoke" activity that contains a "magic" code. This code MUST // then be used to try fetching the token from Botframework service within some time // period. We try here. If it succeeds, we return 200 with an empty body. If it fails // with a retriable error, we return 500. Teams will re-send another invoke in this case. // If it fails with a non-retriable error, we return 404. Teams will not (still work in // progress) retry in that case. var userId = turnContext.Activity.From.Id; var channelId = turnContext.Activity.ChannelId; var magicCode = (turnContext.Activity.Value as JObject)?.GetValue("state", StringComparison.Ordinal)?.ToString(); var tokenResponse = await userTokenClient.GetUserTokenAsync(userId, connectionName, channelId, magicCode, cancellationToken).ConfigureAwait(false); await turnContext.SendActivityAsync(CreateInvokeResponseActivity(tokenResponse != null ? HttpStatusCode.OK : HttpStatusCode.NotFound), cancellationToken).ConfigureAwait(false); return(tokenResponse); }
private TurnContext CreateTurnContext(Activity activity, ClaimsIdentity claimsIdentity, string oauthScope, IConnectorClient connectorClient, UserTokenClient userTokenClient, BotCallbackHandler callback, ConnectorFactory connectorFactory) { var turnContext = new TurnContext(this, activity); turnContext.TurnState.Add <IIdentity>(BotIdentityKey, claimsIdentity); turnContext.TurnState.Add(connectorClient); turnContext.TurnState.Add(userTokenClient); turnContext.TurnState.Add(callback); turnContext.TurnState.Add(connectorFactory); turnContext.TurnState.Set(OAuthScopeKey, oauthScope); // in non-skills scenarios the oauth scope value here will be null, so use Set return(turnContext); }
private static async Task <DialogTurnResult> OnContinueWithMessageActivityAsync(DialogContext dc, UserTokenClient userTokenClient, string connectionName, PromptValidator <TokenResponse> validator, bool endOnInvalidMessage, CancellationToken cancellationToken) { var tokenResponse = await OAuthHelper.CreateTokenResponseFromMessageAsync(userTokenClient, connectionName, dc.Context.Activity, cancellationToken).ConfigureAwait(false); // Call any custom validation we might have. if (await CheckValidatorAsync(dc, validator, tokenResponse, cancellationToken).ConfigureAwait(false)) { return(await dc.EndDialogAsync(tokenResponse, cancellationToken).ConfigureAwait(false)); } else { // If EndOnInvalidMessage is set, complete the prompt with no result. if (endOnInvalidMessage) { return(await dc.EndDialogAsync(null, cancellationToken).ConfigureAwait(false)); } else { // If this was a message Activity we can use the retry prompt var promptOptions = dc.ActiveDialog.State[OAuthHelper.PersistedOptions].CastTo <PromptOptions>(); if (!dc.Context.Responded && promptOptions?.RetryPrompt != null) { await dc.Context.SendActivityAsync(promptOptions.RetryPrompt, cancellationToken).ConfigureAwait(false); } return(EndOfTurn); } } }
/// <summary> /// Helper function used in BeginDialog. /// </summary> /// <param name="userTokenClient">The userTokenClient.</param> /// <param name="settings">The oauth settings.</param> /// <param name="turnContext">The turncontext.</param> /// <param name="prompt">A message activity for the prompt.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A Task.</returns> public static async Task SendOAuthCardAsync(UserTokenClient userTokenClient, OAuthPromptSettings settings, ITurnContext turnContext, IMessageActivity prompt, CancellationToken cancellationToken) { // Ensure prompt initialized prompt ??= Activity.CreateMessageActivity(); if (prompt.Attachments == null) { prompt.Attachments = new List <Attachment>(); } // Append appropriate card if missing if (!ChannelSupportsOAuthCard(turnContext.Activity.ChannelId)) { if (!prompt.Attachments.Any(a => a.Content is SigninCard)) { var signInResource = await userTokenClient.GetSignInResourceAsync(settings.ConnectionName, turnContext.Activity, null, cancellationToken).ConfigureAwait(false); prompt.Attachments.Add(new Attachment { ContentType = SigninCard.ContentType, Content = new SigninCard { Text = settings.Text, Buttons = new[] { new CardAction { Title = settings.Title, Value = signInResource.SignInLink, Type = ActionTypes.Signin, }, }, }, }); } } else if (!prompt.Attachments.Any(a => a.Content is OAuthCard)) { var cardActionType = ActionTypes.Signin; var signInResource = await userTokenClient.GetSignInResourceAsync(settings.ConnectionName, turnContext.Activity, null, cancellationToken).ConfigureAwait(false); var value = signInResource.SignInLink; // use the SignInLink when // in speech channel or // bot is a skill or // TODO: the OauthPrompt code also checked for || settings.OAuthAppCredentials != null if (turnContext.Activity.IsFromStreamingConnection() || IsSkill(turnContext.TurnState.Get <ClaimsIdentity>(BotAdapter.BotIdentityKey))) { if (turnContext.Activity.ChannelId == Channels.Emulator) { cardActionType = ActionTypes.OpenUrl; } } else if (!ChannelRequiresSignInLink(turnContext.Activity.ChannelId)) { value = null; } prompt.Attachments.Add(new Attachment { ContentType = OAuthCard.ContentType, Content = new OAuthCard { Text = settings.Text, ConnectionName = settings.ConnectionName, Buttons = new[] { new CardAction { Title = settings.Title, Text = settings.Text, Type = cardActionType, Value = value }, }, TokenExchangeResource = signInResource.TokenExchangeResource, }, }); } // Add the login timeout specified in OAuthPromptSettings to TurnState so it can be referenced if polling is needed if (!turnContext.TurnState.ContainsKey(TurnStateConstants.OAuthLoginTimeoutKey) && settings.Timeout.HasValue) { turnContext.TurnState.Add <object>(TurnStateConstants.OAuthLoginTimeoutKey, TimeSpan.FromMilliseconds(settings.Timeout.Value)); } // Set input hint if (string.IsNullOrEmpty(prompt.InputHint)) { prompt.InputHint = InputHints.AcceptingInput; } await turnContext.SendActivityAsync(prompt, cancellationToken).ConfigureAwait(false); }
private static async Task <TokenResponse> OnContinueTokenExchangeAsync(UserTokenClient userTokenClient, string connectionName, ITurnContext turnContext, CancellationToken cancellationToken) { var tokenExchangeInvokeRequest = ((JObject)turnContext.Activity.Value)?.ToObject <TokenExchangeInvokeRequest>(); HttpStatusCode httpStatusCode; TokenExchangeInvokeResponse tokenExchangeInvokeResponse; TokenResponse tokenResponse; if (tokenExchangeInvokeRequest == null) { httpStatusCode = HttpStatusCode.BadRequest; tokenExchangeInvokeResponse = new TokenExchangeInvokeResponse { ConnectionName = connectionName, FailureDetail = "The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value. This is required to be sent with the InvokeActivity.", }; tokenResponse = null; } else if (tokenExchangeInvokeRequest.ConnectionName != connectionName) { httpStatusCode = HttpStatusCode.BadRequest; tokenExchangeInvokeResponse = new TokenExchangeInvokeResponse { Id = tokenExchangeInvokeRequest.Id, ConnectionName = connectionName, FailureDetail = $"The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a ConnectionName {tokenExchangeInvokeRequest.ConnectionName} that does not match the ConnectionName {connectionName} expected by the bot's active OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid ConnectionName in the TokenExchangeInvokeRequest", }; tokenResponse = null; } else { // inbound invoke activity appears valid so we will call the user token service var userId = turnContext.Activity.From.Id; var channelId = turnContext.Activity.ChannelId; var tokenExchangeResponse = await userTokenClient.ExchangeTokenAsync(userId, connectionName, channelId, new TokenExchangeRequest { Token = tokenExchangeInvokeRequest.Token }, cancellationToken).ConfigureAwait(false); if (tokenExchangeResponse == null || string.IsNullOrEmpty(tokenExchangeResponse.Token)) { httpStatusCode = HttpStatusCode.PreconditionFailed; tokenExchangeInvokeResponse = new TokenExchangeInvokeResponse { Id = tokenExchangeInvokeRequest.Id, ConnectionName = connectionName, FailureDetail = "The bot is unable to exchange token. Proceed with regular login.", }; tokenResponse = null; } else { httpStatusCode = HttpStatusCode.OK; tokenExchangeInvokeResponse = new TokenExchangeInvokeResponse { Id = tokenExchangeInvokeRequest.Id, ConnectionName = connectionName, }; tokenResponse = new TokenResponse { ChannelId = tokenExchangeResponse.ChannelId, ConnectionName = tokenExchangeResponse.ConnectionName, Token = tokenExchangeResponse.Token, }; } } await turnContext.SendActivityAsync(CreateInvokeResponseActivity(httpStatusCode, tokenExchangeInvokeResponse)).ConfigureAwait(false); return(tokenResponse); }
private static async Task <DialogTurnResult> OnContinueInvokeActivityAsync(DialogContext dc, UserTokenClient userTokenClient, string connectionName, CancellationToken cancellationToken) { var turnContext = dc.Context; switch (turnContext.Activity.Name) { case SignInConstants.VerifyStateOperationName: { var tokenResponse = await OnContinueVerifyStateAsync(userTokenClient, connectionName, turnContext, cancellationToken).ConfigureAwait(false); return(await dc.EndDialogAsync(tokenResponse, cancellationToken).ConfigureAwait(false)); } case SignInConstants.TokenExchangeOperationName: { var tokenResponse = await OnContinueTokenExchangeAsync(userTokenClient, connectionName, turnContext, cancellationToken).ConfigureAwait(false); return(await dc.EndDialogAsync(tokenResponse, cancellationToken).ConfigureAwait(false)); } default: throw new Exception($"unexpected Invoke Activity name {turnContext.Activity.Name}"); } }
/// <summary> /// Method to process Event and Invoke activities that are part of the Bot Framework Protocol oauth support. /// </summary> /// <param name="dc">The DialogContext.</param> /// <param name="userTokenClient">The user token client.</param> /// <param name="connectionName">The ConnectionName.</param> /// <param name="cancellationToken">CancellationToken.</param> /// <returns>A Task of DialogTurnResult.</returns> public static Task <DialogTurnResult> OnContinueWithNonMessageActivityAsync(DialogContext dc, UserTokenClient userTokenClient, string connectionName, CancellationToken cancellationToken) { switch (dc.Context.Activity.Type) { case ActivityTypes.Event: return(OnContinueEventActivityAsync(dc, cancellationToken)); case ActivityTypes.Invoke: return(OnContinueInvokeActivityAsync(dc, userTokenClient, connectionName, cancellationToken)); default: // unexpected Activity type return(Task.FromResult(Dialog.EndOfTurn)); } }