/// <summary> /// Called when the dialog is started and pushed onto the dialog stack. /// </summary> /// <param name="dc">The <see cref="DialogContext"/> for the current turn of conversation.</param> /// <param name="options">Optional, initial information to pass to the dialog.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public override async Task <DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default) { if (Disabled != null && Disabled.GetValue(dc.State)) { return(await dc.EndDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false)); } // Update the dialog options with the runtime settings. DialogOptions.BotId = BotId.GetValue(dc.State); DialogOptions.SkillHostEndpoint = new Uri(SkillHostEndpoint.GetValue(dc.State)); DialogOptions.ConversationIdFactory = dc.Context.TurnState.Get <SkillConversationIdFactoryBase>() ?? throw new NullReferenceException("Unable to locate SkillConversationIdFactoryBase in HostContext"); DialogOptions.SkillClient = dc.Context.TurnState.Get <BotFrameworkClient>() ?? throw new NullReferenceException("Unable to locate BotFrameworkClient in HostContext"); DialogOptions.ConversationState = dc.Context.TurnState.Get <ConversationState>() ?? throw new NullReferenceException($"Unable to get an instance of {nameof(ConversationState)} from TurnState."); DialogOptions.ConnectionName = ConnectionName.GetValue(dc.State); // Set the skill to call DialogOptions.Skill.Id = DialogOptions.Skill.AppId = SkillAppId.GetValue(dc.State); DialogOptions.Skill.SkillEndpoint = new Uri(SkillEndpoint.GetValue(dc.State)); // Store the initialized DialogOptions in state so we can restore these values when the dialog is resumed. dc.ActiveDialog.State[_dialogOptionsStateKey] = DialogOptions; // Get the activity to send to the skill. Activity activity = null; if (Activity != null && ActivityProcessed.GetValue(dc.State)) { // The parent consumed the activity in context, use the Activity property to start the skill. activity = await Activity.BindAsync(dc, cancellationToken : cancellationToken).ConfigureAwait(false); } // Call the base to invoke the skill // (If the activity has not been processed, send the turn context activity to the skill (pass through)). return(await base.BeginDialogAsync(dc, new BeginSkillDialogOptions { Activity = activity ?? dc.Context.Activity }, cancellationToken).ConfigureAwait(false)); }
private Task <PromptRecognizerResult <TokenResponse> > RecognizeTokenAsync(DialogContext dc, CancellationToken cancellationToken) { var settings = new OAuthPromptSettings { ConnectionName = ConnectionName.GetValue(dc.State) }; return(OAuthPrompt.RecognizeTokenAsync(settings, dc, cancellationToken)); }
private Task SendOAuthCardAsync(DialogContext dc, IMessageActivity prompt, CancellationToken cancellationToken) { var settings = new OAuthPromptSettings { ConnectionName = ConnectionName?.GetValue(dc.State), Title = Title?.GetValue(dc.State), Text = Text?.GetValue(dc.State) }; return(OAuthPrompt.SendOAuthCardAsync(settings, dc.Context, prompt, cancellationToken)); }
/// <summary> /// Signs out the user. /// </summary> /// <param name="dc">DialogContext for the current turn of conversation with the user.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> public async Task SignOutUserAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) { var settings = new OAuthPromptSettings { ConnectionName = ConnectionName?.GetValue(dc.State) }; await new OAuthPrompt(nameof(OAuthPrompt), settings).SignOutUserAsync(dc.Context, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Attempts to get the user's token. /// </summary> /// <param name="dc">DialogContext for the current turn of conversation with the user.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the task is successful and user already has a token or the user successfully signs in, /// the result contains the user's token.</remarks> public async Task <TokenResponse> GetUserTokenAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) { var settings = new OAuthPromptSettings { ConnectionName = ConnectionName?.GetValue(dc.State) }; return(await new OAuthPrompt(nameof(OAuthPrompt), settings).GetUserTokenAsync(dc.Context, cancellationToken).ConfigureAwait(false)); }
/// <summary> /// Attempts to get the user's token. /// </summary> /// <param name="dc">DialogContext for the current turn of conversation with the user.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the task is successful and user already has a token or the user successfully signs in, /// the result contains the user's token.</remarks> public async Task <TokenResponse> GetUserTokenAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) { if (!(dc.Context.Adapter is IUserTokenProvider adapter)) { throw new InvalidOperationException("OAuthPrompt.GetUserToken(): not supported by the current adapter"); } return(await adapter.GetUserTokenAsync(dc.Context, ConnectionName.GetValue(dc.State), null, cancellationToken).ConfigureAwait(false)); }
/// <summary> /// Signs out the user. /// </summary> /// <param name="dc">DialogContext for the current turn of conversation with the user.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> public async Task SignOutUserAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) { if (!(dc.Context.Adapter is IUserTokenProvider adapter)) { throw new InvalidOperationException("OAuthPrompt.SignOutUser(): not supported by the current adapter"); } // Sign out user await adapter.SignOutUserAsync(dc.Context, ConnectionName.GetValue(dc.State), dc.Context.Activity?.From?.Id, cancellationToken).ConfigureAwait(false); }
private async Task SendOAuthCardAsync(DialogContext dc, IMessageActivity prompt, CancellationToken cancellationToken) { var title = await Title.GetValueAsync(dc, cancellationToken).ConfigureAwait(false); var text = await Text.GetValueAsync(dc, cancellationToken).ConfigureAwait(false); var settings = new OAuthPromptSettings { ConnectionName = ConnectionName?.GetValue(dc.State), Title = title, Text = text }; await OAuthPrompt.SendOAuthCardAsync(settings, dc.Context, prompt, cancellationToken).ConfigureAwait(false); }
private async Task SendOAuthCardAsync(DialogContext dc, IMessageActivity prompt, CancellationToken cancellationToken) { // Save state prior to sending OAuthCard: the invoke response for a token exchange from the root bot could come in // before this method ends or could land in another instance in scale-out scenarios, which means that if the state is not saved, // the OAuthInput would not be at the top of the stack, and the token exchange invoke would get discarded. await dc.Context.TurnState.Get <ConversationState>().SaveChangesAsync(dc.Context, false, cancellationToken).ConfigureAwait(false); // Prepare OAuthCard var title = Title == null ? null : await Title.GetValueAsync(dc, cancellationToken).ConfigureAwait(false); var text = Text == null ? null : await Text.GetValueAsync(dc, cancellationToken).ConfigureAwait(false); var settings = new OAuthPromptSettings { ConnectionName = ConnectionName?.GetValue(dc.State), Title = title, Text = text }; // Send OAuthCard to root bot. The root bot could attempt to do a token exchange or if it cannot do token exchange for this connection // it will let the card get to the user to allow them to sign in. await OAuthPrompt.SendOAuthCardAsync(settings, dc.Context, prompt, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Called when a prompt dialog is pushed onto the dialog stack and is being activated. /// </summary> /// <param name="dc">The dialog context for the current turn of the conversation.</param> /// <param name="options">Optional, additional information to pass to the prompt being started.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <remarks>If the task is successful, the result indicates whether the prompt is still /// active after the turn has been processed by the prompt.</remarks> public override async Task <DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken)) { if (dc == null) { throw new ArgumentNullException(nameof(dc)); } if (options is CancellationToken) { throw new ArgumentException($"{nameof(options)} cannot be a cancellation token"); } if (this.Disabled != null && this.Disabled.GetValue(dc.State)) { return(await dc.EndDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false)); } PromptOptions opt = null; if (options != null) { if (options is PromptOptions) { // Ensure prompts have input hint set opt = options as PromptOptions; if (opt.Prompt != null && string.IsNullOrEmpty(opt.Prompt.InputHint)) { opt.Prompt.InputHint = InputHints.AcceptingInput; } if (opt.RetryPrompt != null && string.IsNullOrEmpty(opt.RetryPrompt.InputHint)) { opt.RetryPrompt.InputHint = InputHints.AcceptingInput; } } } var op = OnInitializeOptions(dc, options); dc.State.SetValue(ThisPath.Options, op); dc.State.SetValue(TURN_COUNT_PROPERTY, 0); // If AlwaysPrompt is set to true, then clear Property value for turn 0. if (this.Property != null && this.AlwaysPrompt != null && this.AlwaysPrompt.GetValue(dc.State)) { dc.State.SetValue(this.Property.GetValue(dc.State), null); } // Initialize state var state = dc.ActiveDialog.State; state[PersistedOptions] = opt; state[PersistedState] = new Dictionary <string, object> { { AttemptCountKey, 0 }, }; state[PersistedExpires] = DateTime.Now.AddMilliseconds(Timeout.GetValue(dc.State)); // Attempt to get the users token if (!(dc.Context.Adapter is IUserTokenProvider adapter)) { throw new InvalidOperationException("OAuthPrompt.Recognize(): not supported by the current adapter"); } var output = await adapter.GetUserTokenAsync(dc.Context, ConnectionName.GetValue(dc.State), null, cancellationToken).ConfigureAwait(false); if (output != null) { if (this.Property != null) { dc.State.SetValue(this.Property.GetValue(dc.State), output); } // Return token return(await dc.EndDialogAsync(output, cancellationToken).ConfigureAwait(false)); } else { dc.State.SetValue(TURN_COUNT_PROPERTY, 1); // Prompt user to login await SendOAuthCardAsync(dc, opt?.Prompt, cancellationToken).ConfigureAwait(false); return(Dialog.EndOfTurn); } }
private async Task <PromptRecognizerResult <TokenResponse> > RecognizeTokenAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) { var result = new PromptRecognizerResult <TokenResponse>(); if (IsTokenResponseEvent(dc.Context)) { var tokenResponseObject = dc.Context.Activity.Value as JObject; var token = tokenResponseObject?.ToObject <TokenResponse>(); result.Succeeded = true; result.Value = token; } else if (IsTeamsVerificationInvoke(dc.Context)) { var magicCodeObject = dc.Context.Activity.Value as JObject; var magicCode = magicCodeObject.GetValue("state")?.ToString(); if (!(dc.Context.Adapter is IUserTokenProvider adapter)) { throw new InvalidOperationException("OAuthPrompt.Recognize(): not supported by the current adapter"); } // 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 failes with a non-retriable error, we return 404. Teams will not (still work in // progress) retry in that case. try { var token = await adapter.GetUserTokenAsync(dc.Context, ConnectionName.GetValue(dc.State), magicCode, cancellationToken).ConfigureAwait(false); if (token != null) { result.Succeeded = true; result.Value = token; await dc.Context.SendActivityAsync(new Activity { Type = ActivityTypesEx.InvokeResponse }, cancellationToken).ConfigureAwait(false); } else { await dc.Context.SendActivityAsync(new Activity { Type = ActivityTypesEx.InvokeResponse, Value = new InvokeResponse { Status = 404 } }, cancellationToken).ConfigureAwait(false); } } catch { await dc.Context.SendActivityAsync(new Activity { Type = ActivityTypesEx.InvokeResponse, Value = new InvokeResponse { Status = 500 } }, cancellationToken).ConfigureAwait(false); } } else if (dc.Context.Activity.Type == ActivityTypes.Message) { var matched = _magicCodeRegex.Match(dc.Context.Activity.Text); if (matched.Success) { if (!(dc.Context.Adapter is IUserTokenProvider adapter)) { throw new InvalidOperationException("OAuthPrompt.Recognize(): not supported by the current adapter"); } var token = await adapter.GetUserTokenAsync(dc.Context, ConnectionName.GetValue(dc.State), matched.Value, cancellationToken).ConfigureAwait(false); if (token != null) { result.Succeeded = true; result.Value = token; } } } return(result); }
private async Task SendOAuthCardAsync(DialogContext dc, IMessageActivity prompt, CancellationToken cancellationToken = default(CancellationToken)) { var turnContext = dc.Context; if (!(turnContext.Adapter is IUserTokenProvider adapter)) { throw new InvalidOperationException("OAuthPrompt.Prompt(): not supported by the current adapter"); } // Ensure prompt initialized if (prompt == null) { 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 link = await adapter.GetOauthSignInLinkAsync(turnContext, ConnectionName?.GetValue(dc.State), cancellationToken).ConfigureAwait(false); prompt.Attachments.Add(new Attachment { ContentType = SigninCard.ContentType, Content = new SigninCard { Text = Text?.GetValue(dc.State), Buttons = new[] { new CardAction { Title = Title?.GetValue(dc.State), Value = link, Type = ActionTypes.Signin, }, }, }, }); } } else if (!prompt.Attachments.Any(a => a.Content is OAuthCard)) { prompt.Attachments.Add(new Attachment { ContentType = OAuthCard.ContentType, Content = new OAuthCard { Text = Text?.GetValue(dc.State), ConnectionName = ConnectionName?.GetValue(dc.State), Buttons = new[] { new CardAction { Title = Title?.GetValue(dc.State), Text = Text?.GetValue(dc.State), Type = ActionTypes.Signin, }, }, }, }); } // Set input hint if (string.IsNullOrEmpty(prompt.InputHint)) { prompt.InputHint = InputHints.AcceptingInput; } await turnContext.SendActivityAsync(prompt, cancellationToken).ConfigureAwait(false); }
private async Task SendOAuthCardAsync(DialogContext dc, IMessageActivity prompt, CancellationToken cancellationToken = default(CancellationToken)) { var turnContext = dc.Context; BotAssert.ContextNotNull(turnContext); if (!(turnContext.Adapter is IExtendedUserTokenProvider adapter)) { throw new InvalidOperationException("OAuthPrompt.Prompt(): not supported by the current adapter"); } // Ensure prompt initialized if (prompt == null) { 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 adapter.GetSignInResourceAsync(turnContext, null, ConnectionName?.GetValue(dc.State), turnContext.Activity.From.Id, null, cancellationToken).ConfigureAwait(false); prompt.Attachments.Add(new Attachment { ContentType = SigninCard.ContentType, Content = new SigninCard { Text = Text?.GetValue(dc.State), Buttons = new[] { new CardAction { Title = Title?.GetValue(dc.State), Value = signInResource.SignInLink, Type = ActionTypes.Signin, }, }, }, }); } } else if (!prompt.Attachments.Any(a => a.Content is OAuthCard)) { var cardActionType = ActionTypes.Signin; var signInResource = await adapter.GetSignInResourceAsync(turnContext, null, ConnectionName?.GetValue(dc.State), turnContext.Activity.From.Id, null, cancellationToken).ConfigureAwait(false); var value = signInResource.SignInLink; // use the SignInLink when // in speech channel or // bot is a skill or // an extra OAuthAppCredentials is being passed in if (turnContext.Activity.IsFromStreamingConnection() || (turnContext.TurnState.Get <ClaimsIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && SkillValidation.IsSkillClaim(botIdentity.Claims)) || null != null) { if (turnContext.Activity.ChannelId == Channels.Emulator) { cardActionType = ActionTypes.OpenUrl; } } else { value = null; } var text = Text?.GetValue(dc.State); var connectionName = ConnectionName?.GetValue(dc.State); var title = Title?.GetValue(dc.State); prompt.Attachments.Add(new Attachment { ContentType = OAuthCard.ContentType, Content = new OAuthCard { Text = text, ConnectionName = connectionName, Buttons = new[] { new CardAction { Title = title, Text = text, Type = cardActionType, Value = value }, }, TokenExchangeResource = signInResource.TokenExchangeResource, }, }); }