/// <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));
        }
예제 #2
0
        private Task <PromptRecognizerResult <TokenResponse> > RecognizeTokenAsync(DialogContext dc, CancellationToken cancellationToken)
        {
            var settings = new OAuthPromptSettings {
                ConnectionName = ConnectionName.GetValue(dc.State)
            };

            return(OAuthPrompt.RecognizeTokenAsync(settings, dc, cancellationToken));
        }
예제 #3
0
        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));
        }
예제 #4
0
        /// <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);
        }
예제 #5
0
        /// <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));
        }
예제 #6
0
        /// <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));
        }
예제 #7
0
        /// <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);
        }
예제 #9
0
        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);
        }
예제 #10
0
        /// <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);
            }
        }
예제 #11
0
        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);
        }
예제 #12
0
        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);
        }
예제 #13
0
        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,
                    },
                });
            }