Example #1
0
 public async Task PostAsync()
 {
     // Delegate the processing of the HTTP POST to the adapter.
     // The adapter will invoke the bot.
     var bot = SkillValidation.IsSkillToken(Request.Headers["Authorization"]) ? _echoBot : _rootBot;
     await _adapter.ProcessAsync(Request, Response, bot);
 }
Example #2
0
        /// <summary>
        /// Creates the connector client asynchronous.
        /// </summary>
        /// <param name="serviceUrl">The service URL.</param>
        /// <param name="claimsIdentity">The claims identity.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>ConnectorClient instance.</returns>
        /// <exception cref="NotSupportedException">ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off.</exception>
        private async Task <IConnectorClient> CreateConnectorClientAsync(string serviceUrl, ClaimsIdentity claimsIdentity, CancellationToken cancellationToken)
        {
            if (claimsIdentity == null)
            {
                throw new NotSupportedException("ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off.");
            }

            // For requests from channel App Id is in Audience claim of JWT token. For emulator it is in AppId claim. For
            // unauthenticated requests we have anonymous identity provided auth is disabled.
            // For Activities coming from Emulator AppId claim contains the Bot's AAD AppId.
            var botAppIdClaim = claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim);

            if (botAppIdClaim == null)
            {
                botAppIdClaim = claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AppIdClaim);
            }

            // For anonymous requests (requests with no header) appId is not set in claims.
            AppCredentials appCredentials = null;

            if (botAppIdClaim != null)
            {
                var    botId = botAppIdClaim.Value;
                string scope = null;
                if (SkillValidation.IsSkillClaim(claimsIdentity.Claims))
                {
                    // The skill connector has the target skill in the OAuthScope.
                    scope = JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims);
                }

                appCredentials = await GetAppCredentialsAsync(botId, scope, cancellationToken).ConfigureAwait(false);
            }

            return(CreateConnectorClient(serviceUrl, appCredentials));
        }
Example #3
0
        /// <summary>
        /// This method is called from JwtTokenValidation.ValidateClaimsAsync
        /// </summary>
        /// <param name="claims"></param>
        public override Task ValidateClaimsAsync(IList <Claim> claims)
        {
            if (claims == null)
            {
                throw new ArgumentNullException(nameof(claims));
            }

            if (!claims.Any())
            {
                throw new UnauthorizedAccessException("ValidateClaimsAsync.claims parameter must contain at least one element.");
            }

            if (SkillValidation.IsSkillClaim(claims))
            {
                // if _allowedCallers has one item of '*', allow all parent bot calls and do not validate the appid from claims
                if (_allowedCallers.Count == 1 && _allowedCallers[0] == "*")
                {
                    return(Task.CompletedTask);
                }

                // Check that the appId claim in the skill request is in the list of skills configured for this bot.
                var appId = JwtTokenValidation.GetAppIdFromClaims(claims).ToUpperInvariant();
                if (_allowedCallers.Contains(appId))
                {
                    return(Task.CompletedTask);
                }

                throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this caller, add the app ID to your configuration file.");
            }

            throw new UnauthorizedAccessException($"ValidateClaimsAsync called without a Skill claim in claims.");
        }
Example #4
0
        public void CreateAnonymousSkillClaimTest()
        {
            var sut = SkillValidation.CreateAnonymousSkillClaim();

            Assert.Equal(AuthenticationConstants.AnonymousSkillAppId, JwtTokenValidation.GetAppIdFromClaims(sut.Claims));
            Assert.Equal(AuthenticationConstants.AnonymousAuthType, sut.AuthenticationType);
        }
        public void IsSkillClaimTest()
        {
            var claims   = new List <Claim>();
            var audience = Guid.NewGuid().ToString();
            var appId    = Guid.NewGuid().ToString();

            // Empty list of claims
            Assert.False(SkillValidation.IsSkillClaim(claims));

            // No Audience claim
            claims.Add(new Claim(AuthenticationConstants.VersionClaim, "1.0"));
            Assert.False(SkillValidation.IsSkillClaim(claims));

            // Emulator Audience claim
            claims.Add(new Claim(AuthenticationConstants.AudienceClaim, AuthenticationConstants.ToBotFromChannelTokenIssuer));
            Assert.False(SkillValidation.IsSkillClaim(claims));

            // No AppId claim
            claims.RemoveAt(claims.Count - 1);
            claims.Add(new Claim(AuthenticationConstants.AudienceClaim, audience));
            Assert.False(SkillValidation.IsSkillClaim(claims));

            // AppId != Audience
            claims.Add(new Claim(AuthenticationConstants.AppIdClaim, audience));
            Assert.False(SkillValidation.IsSkillClaim(claims));

            // All checks pass, should be good now
            claims.RemoveAt(claims.Count - 1);
            claims.Add(new Claim(AuthenticationConstants.AppIdClaim, appId));
            Assert.True(SkillValidation.IsSkillClaim(claims));
        }
Example #6
0
 public void Initialize()
 {
     skillService        = new Mock <ISkillService>();
     contactSkillService = new Mock <IContactSkillService>();
     skillValidation     = new SkillValidation(contactSkillService.Object);
     logger = new Mock <ILogger <Skill> >();
     httpContextAccessor = new Mock <IHttpContextAccessor>();
 }
        public async Task IdentityValidationTests()
        {
            var mockCredentials = new Mock <ICredentialProvider>();
            var audience        = Guid.NewGuid().ToString();
            var appId           = Guid.NewGuid().ToString();
            var mockIdentity    = new Mock <ClaimsIdentity>();
            var claims          = new List <Claim>();

            // Null identity
            var exception = await Assert.ThrowsAsync <UnauthorizedAccessException>(
                async() => await SkillValidation.ValidateIdentityAsync(null, mockCredentials.Object));

            Assert.Equal("Invalid Identity", exception.Message);

            // not authenticated identity
            mockIdentity.Setup(x => x.IsAuthenticated).Returns(false);
            exception = await Assert.ThrowsAsync <UnauthorizedAccessException>(
                async() => await SkillValidation.ValidateIdentityAsync(mockIdentity.Object, mockCredentials.Object));

            Assert.Equal("Token Not Authenticated", exception.Message);

            // No version claims
            mockIdentity.Setup(x => x.IsAuthenticated).Returns(true);
            mockIdentity.Setup(x => x.Claims).Returns(claims);
            exception = await Assert.ThrowsAsync <UnauthorizedAccessException>(
                async() => await SkillValidation.ValidateIdentityAsync(mockIdentity.Object, mockCredentials.Object));

            Assert.Equal($"'{AuthenticationConstants.VersionClaim}' claim is required on skill Tokens.", exception.Message);

            // No audience claim
            claims.Add(new Claim(AuthenticationConstants.VersionClaim, "1.0"));
            exception = await Assert.ThrowsAsync <UnauthorizedAccessException>(
                async() => await SkillValidation.ValidateIdentityAsync(mockIdentity.Object, mockCredentials.Object));

            Assert.Equal($"'{AuthenticationConstants.AudienceClaim}' claim is required on skill Tokens.", exception.Message);

            // Invalid AppId in audience
            claims.Add(new Claim(AuthenticationConstants.AudienceClaim, audience));
            mockCredentials.Setup(x => x.IsValidAppIdAsync(It.IsAny <string>())).Returns(Task.FromResult(false));
            exception = await Assert.ThrowsAsync <UnauthorizedAccessException>(
                async() => await SkillValidation.ValidateIdentityAsync(mockIdentity.Object, mockCredentials.Object));

            Assert.Equal("Invalid audience.", exception.Message);

            // Invalid AppId in in appId or azp
            mockCredentials.Setup(x => x.IsValidAppIdAsync(It.IsAny <string>())).Returns(Task.FromResult(true));
            exception = await Assert.ThrowsAsync <UnauthorizedAccessException>(
                async() => await SkillValidation.ValidateIdentityAsync(mockIdentity.Object, mockCredentials.Object));

            Assert.Equal("Invalid appId.", exception.Message);

            // All checks pass (no exception thrown)
            claims.Add(new Claim(AuthenticationConstants.AppIdClaim, appId));
            await SkillValidation.ValidateIdentityAsync(mockIdentity.Object, mockCredentials.Object);
        }
        /// <summary>
        /// Checks that the appId claim in the skill request is in the list of skills configured for this bot.
        /// </summary>
        /// <param name="claims">The list of claims to validate.</param>
        /// <returns>A task that represents the work queued to execute.</returns>
        public override Task ValidateClaimsAsync(IList <Claim> claims)
        {
            if (SkillValidation.IsSkillClaim(claims))
            {
                var appId = JwtTokenValidation.GetAppIdFromClaims(claims);
                if (!_allowedSkills.Contains(appId))
                {
                    throw new UnauthorizedAccessException($"Received a request from an application with an appID of \"{appId}\". To enable requests from this skill, add the skill to your configuration file.");
                }
            }

            return(Task.CompletedTask);
        }
Example #9
0
        /// <summary>
        /// Checks that the appId claim in the skill request is in the list of callers configured for this bot.
        /// </summary>
        /// <param name="claims">The list of claims to validate.</param>
        /// <returns>A task that represents the work queued to execute.</returns>
        public override Task ValidateClaimsAsync(IList <Claim> claims)
        {
            // if _allowedCallers is null we allow all calls
            if (_allowedCallers != null && SkillValidation.IsSkillClaim(claims))
            {
                var appId = JwtTokenValidation.GetAppIdFromClaims(claims);
                if (!_allowedCallers.Contains(appId))
                {
                    throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this caller, add the app ID to your configuration file.");
                }
            }

            return(Task.CompletedTask);
        }
Example #10
0
        public override Task ValidateClaimsAsync(IList <Claim> claims)
        {
            if (SkillValidation.IsSkillClaim(claims))
            {
                // Check that the appId claim in the skill request is in the list of skills or callers configured for this bot.
                var appId = JwtTokenValidation.GetAppIdFromClaims(claims);
                if (!_allowedCallers.Contains(appId) && !_allowedSkills.Contains(appId))
                {
                    throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this skill or caller, add the app ID to your configuration file.");
                }
            }

            return(Task.CompletedTask);
        }
Example #11
0
        public override Task ValidateClaimsAsync(IList <Claim> claims)
        {
            // if _allowedCallers has one item of '*', allow all parent bot calls and do not validate the appid from claims
            if (SkillValidation.IsSkillClaim(claims) && !(_allowedCallers.Count == 1 && _allowedCallers[0] == "*"))
            {
                // Check that the appId claim in the skill request is in the list of skills configured for this bot.
                var appId = JwtTokenValidation.GetAppIdFromClaims(claims);
                if (!_allowedCallers.Contains(appId))
                {
                    throw new UnauthorizedAccessException($"Received a request from a bot with an app ID of \"{appId}\". To enable requests from this caller, add the app ID to your configuration file.");
                }
            }

            return(Task.CompletedTask);
        }
        /// <summary>
        /// Get the audience for the WebSocket connection from the authenticated ClaimsIdentity.
        /// </summary>
        /// <remarks>
        /// Setting the Audience on the StreamingRequestHandler enables the bot to call skills and correctly forward responses from the skill to the next recipient.
        /// i.e. the participant at the other end of the WebSocket connection.
        /// </remarks>
        /// <param name="claimsIdentity">ClaimsIdentity for authenticated caller.</param>
        private string GetAudience(ClaimsIdentity claimsIdentity)
        {
            if (claimsIdentity.AuthenticationType != AuthenticationConstants.AnonymousAuthType)
            {
                var audience = ChannelProvider != null && ChannelProvider.IsGovernment() ?
                               GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope :
                               AuthenticationConstants.ToChannelFromBotOAuthScope;

                if (SkillValidation.IsSkillClaim(claimsIdentity.Claims))
                {
                    audience = JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims);
                }

                return(audience);
            }

            return(null);
        }
        /// <summary>
        /// Helper to authenticate the header.
        /// </summary>
        /// <remarks>
        /// This code is very similar to the code in <see cref="JwtTokenValidation.AuthenticateRequest(IActivity, string, ICredentialProvider, IChannelProvider, AuthenticationConfiguration, HttpClient)"/>,
        /// we should move this code somewhere in that library when we refactor auth, for now we keep it private to avoid adding more public static
        /// functions that we will need to deprecate later.
        /// </remarks>
        /// <param name="authHeader">The auth header containing JWT token.</param>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <returns>A <see cref="ClaimsIdentity"/> representing the claims associated with given header.</returns>
        internal override async Task <ClaimsIdentity> AuthenticateAsync(string authHeader, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                var isAuthDisabled = await _credentialProvider.IsAuthenticationDisabledAsync().ConfigureAwait(false);

                if (!isAuthDisabled)
                {
                    // No auth header. Auth is required. Request is not authorized.
                    throw new UnauthorizedAccessException();
                }

                // In the scenario where auth is disabled, we still want to have the
                // IsAuthenticated flag set in the ClaimsIdentity.
                // To do this requires adding in an empty claim.
                // Since ChannelServiceHandler calls are always a skill callback call, we set the skill claim too.
                return(SkillValidation.CreateAnonymousSkillClaim());
            }

            // Validate the header and extract claims.
            return(await JwtTokenValidation.ValidateAuthHeader(authHeader, _credentialProvider, ChannelProvider, "unknown", _authConfiguration).ConfigureAwait(false));
        }
Example #14
0
        /// <summary>
        /// Validate a list of claims and throw an exception if it fails.
        /// </summary>
        /// <param name="claims">The list of claims to validate.</param>
        /// <returns>True if the validation is successful, false if not.</returns>
        public override Task ValidateClaimsAsync(IList <Claim> claims)
        {
            if (claims == null)
            {
                throw new ArgumentNullException(nameof(claims));
            }

            // If _allowedCallers contains an "*", allow all callers.
            if (SkillValidation.IsSkillClaim(claims) &&
                !_allowedCallers.Contains("*"))
            {
                // Check that the appId claim in the skill request is in the list of callers configured for this bot.
                var applicationId = JwtTokenValidation.GetAppIdFromClaims(claims);
                if (!_allowedCallers.Contains(applicationId))
                {
                    throw new UnauthorizedAccessException(
                              $"Received a request from a bot with an app ID of \"{applicationId}\". To enable requests from this caller, add the app ID to your \"${ConfigurationConstants.RuntimeSettingsKey}:skill:allowedCallers\" configuration.");
                }
            }

            return(Task.CompletedTask);
        }
        /// <summary>
        /// Helper to determine if we should send an EoC to the parent or not.
        /// </summary>
        private static bool SendEoCToParent(ITurnContext turnContext)
        {
            if (turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims))
            {
                // EoC Activities returned by skills are bounced back to the bot by SkillHandler.
                // In those cases we will have a SkillConversationReference instance in state.
                var skillConversationReference = turnContext.TurnState.Get <SkillConversationReference>(SkillHandler.SkillConversationReferenceKey);
                if (skillConversationReference != null)
                {
                    // If the skillConversationReference.OAuthScope is for one of the supported channels, we are at the root and we should not send an EoC.
                    return(skillConversationReference.OAuthScope != AuthenticationConstants.ToChannelFromBotOAuthScope && skillConversationReference.OAuthScope != GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope);
                }

                return(true);
            }

            return(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,
                    },
                });
            }
Example #17
0
        private async Task SendOAuthCardAsync(ITurnContext turnContext, IMessageActivity prompt, CancellationToken cancellationToken = default(CancellationToken))
        {
            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, _settings.OAuthAppCredentials, _settings.ConnectionName, turnContext.Activity.From.Id, 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 adapter.GetSignInResourceAsync(turnContext, _settings.OAuthAppCredentials, _settings.ConnectionName, turnContext.Activity.From.Id, null, cancellationToken).ConfigureAwait(false);

                var value = signInResource.SignInLink;

                if (turnContext.TurnState.Get <ClaimsIdentity>("BotIdentity") is ClaimsIdentity botIdentity && SkillValidation.IsSkillClaim(botIdentity.Claims))
                {
                    cardActionType = ActionTypes.OpenUrl;
                }
                else
                {
                    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,
                    },
                });
            }
        /// <summary>
        /// Helper to send a trace activity with a memory snapshot of the active dialog DC.
        /// </summary>
        private static async Task SendStateSnapshotTraceAsync(DialogContext dialogContext, CancellationToken cancellationToken)
        {
            var traceLabel = dialogContext.Context.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims)
                ? "Skill State"
                : "Bot State";

            // send trace of memory
            var snapshot      = GetActiveDialogContext(dialogContext).State.GetMemorySnapshot();
            var traceActivity = (Activity)Activity.CreateTraceActivity("BotState", "https://www.botframework.com/schemas/botState", snapshot, traceLabel);
            await dialogContext.Context.SendActivityAsync(traceActivity, cancellationToken).ConfigureAwait(false);
        }
Example #19
0
 private static bool IsSkillBot(ITurnContext turnContext)
 {
     return(turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity &&
            SkillValidation.IsSkillClaim(claimIdentity.Claims));
 }
Example #20
0
        public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
        {
            AdaptiveDialog rootDialog = (AdaptiveDialog)this.dialogManager.RootDialog;

            if (turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims))
            {
                rootDialog.AutoEndDialog = true;
            }
 public void IsSkillTokenTest(bool expected, string testCaseName, string token)
 {
     _output.WriteLine(testCaseName);
     Assert.Equal(expected, SkillValidation.IsSkillToken(token));
 }
 public static bool IsSkill(this ITurnContext turnContext)
 {
     return(turnContext.TurnState.Get <ClaimsIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && SkillValidation.IsSkillClaim(botIdentity.Claims) ? true : false);
 }
        /// <summary>
        /// Runs dialog system in the context of an ITurnContext.
        /// </summary>
        /// <param name="context">turn context.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>result of the running the logic against the activity.</returns>
        public async Task <DialogManagerResult> OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default)
        {
            var botStateSet = new BotStateSet();

            // Preload TurnState with DM TurnState.
            foreach (var pair in TurnState)
            {
                context.TurnState.Set(pair.Key, pair.Value);
            }

            if (ConversationState == null)
            {
                ConversationState = context.TurnState.Get <ConversationState>() ?? throw new ArgumentNullException(nameof(ConversationState));
            }
            else
            {
                context.TurnState.Set(ConversationState);
            }

            botStateSet.Add(ConversationState);

            if (UserState == null)
            {
                UserState = context.TurnState.Get <UserState>();
            }

            if (UserState != null)
            {
                botStateSet.Add(UserState);
            }

            // create property accessors
            var lastAccessProperty = ConversationState.CreateProperty <DateTime>(LastAccess);
            var lastAccess         = await lastAccessProperty.GetAsync(context, () => DateTime.UtcNow, cancellationToken).ConfigureAwait(false);

            // Check for expired conversation
            if (ExpireAfter.HasValue && (DateTime.UtcNow - lastAccess) >= TimeSpan.FromMilliseconds((double)ExpireAfter))
            {
                // Clear conversation state
                await ConversationState.ClearStateAsync(context, cancellationToken).ConfigureAwait(false);
            }

            lastAccess = DateTime.UtcNow;
            await lastAccessProperty.SetAsync(context, lastAccess, cancellationToken).ConfigureAwait(false);

            // get dialog stack
            var dialogsProperty = ConversationState.CreateProperty <DialogState>(_dialogStateProperty);
            var dialogState     = await dialogsProperty.GetAsync(context, () => new DialogState(), cancellationToken).ConfigureAwait(false);

            // Create DialogContext
            var dc = new DialogContext(Dialogs, context, dialogState);

            // get the DialogStateManager configuration
            var dialogStateManager = new DialogStateManager(dc, StateConfiguration);
            await dialogStateManager.LoadAllScopesAsync(cancellationToken).ConfigureAwait(false);

            dc.Context.TurnState.Add(dialogStateManager);

            DialogTurnResult turnResult = null;

            // Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn.
            //
            // NOTE: We loop around this block because each pass through we either complete the turn and break out of the loop
            // or we have had an exception AND there was an OnError action which captured the error.  We need to continue the
            // turn based on the actions the OnError handler introduced.
            var endOfTurn = false;

            while (!endOfTurn)
            {
                try
                {
                    if (context.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims))
                    {
                        // The bot is running as a skill.
                        turnResult = await HandleSkillOnTurnAsync(dc, cancellationToken).ConfigureAwait(false);
                    }
                    else
                    {
                        // The bot is running as root bot.
                        turnResult = await HandleBotOnTurnAsync(dc, cancellationToken).ConfigureAwait(false);
                    }

                    // turn successfully completed, break the loop
                    endOfTurn = true;
                }
        /// <summary>
        /// Shared implementation of the SendOAuthCardAsync function. This is intended for internal use, to
        /// consolidate the implementation of the OAuthPrompt and OAuthInput. Application logic should use
        /// those dialog classes.
        /// </summary>
        /// <param name="settings">OAuthSettings.</param>
        /// <param name="turnContext">ITurnContext.</param>
        /// <param name="prompt">IMessageActivity.</param>
        /// <param name="cancellationToken">CancellationToken.</param>
        /// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
        public static async Task SendOAuthCardAsync(OAuthPromptSettings settings, ITurnContext turnContext, IMessageActivity prompt, CancellationToken cancellationToken)
        {
            BotAssert.ContextNotNull(turnContext);

            // 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 UserTokenAccess.GetSignInResourceAsync(turnContext, settings, 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 UserTokenAccess.GetSignInResourceAsync(turnContext, settings, 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)) ||
                    settings.OAuthAppCredentials != null)
                {
                    if (turnContext.Activity.ChannelId == Channels.Emulator)
                    {
                        cardActionType = ActionTypes.OpenUrl;
                    }
                }
                else if ((settings.ShowSignInLink != null && settings.ShowSignInLink == false) ||
                         (settings.ShowSignInLink == null && !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,
                    },
                });
            }
Example #25
0
        private async Task SendOAuthCardAsync(ITurnContext turnContext, IMessageActivity prompt, CancellationToken cancellationToken = default(CancellationToken))
        {
            BotAssert.ContextNotNull(turnContext);

            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, _settings.ConnectionName, 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 = link,
                                    Type  = ActionTypes.Signin,
                                },
                            },
                        },
                    });
                }
            }
            else if (!prompt.Attachments.Any(a => a.Content is OAuthCard))
            {
                var    cardActionType = ActionTypes.Signin;
                string signInLink     = null;

                if (turnContext.Activity.IsFromStreamingConnection())
                {
                    signInLink = await adapter.GetOauthSignInLinkAsync(turnContext, _settings.ConnectionName, cancellationToken).ConfigureAwait(false);
                }
                else if (turnContext.TurnState.Get <ClaimsIdentity>("BotIdentity") is ClaimsIdentity botIdentity && SkillValidation.IsSkillClaim(botIdentity.Claims))
                {
                    // Force magic code for Skills (to be addressed in R8)
                    signInLink = await adapter.GetOauthSignInLinkAsync(turnContext, _settings.ConnectionName, cancellationToken).ConfigureAwait(false);

                    cardActionType = ActionTypes.OpenUrl;
                }

                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 = signInLink
                            }
                        }
                    }
                });
            }

            // 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 bool IsSkill(ClaimsIdentity botIdentity)
 {
     return(botIdentity == null ? false : SkillValidation.IsSkillClaim(botIdentity.Claims));
 }
        private static bool IsFromParentToSkill(ITurnContext turnContext)
        {
            if (turnContext.TurnState.Get <SkillConversationReference>(SkillHandler.SkillConversationReferenceKey) != null)
            {
                return(false);
            }

            return(turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims));
        }
Example #28
0
        private static CallerInfo CreateCallerInfo(ITurnContext turnContext)
        {
            if (turnContext.TurnState.Get <ClaimsIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity botIdentity && SkillValidation.IsSkillClaim(botIdentity.Claims))
            {
                return(new CallerInfo
                {
                    CallerServiceUrl = turnContext.Activity.ServiceUrl,
                    Scope = JwtTokenValidation.GetAppIdFromClaims(botIdentity.Claims),
                });
            }

            return(null);
        }
        /// <summary>
        /// Creates a dialog stack and starts a dialog, pushing it onto the stack.
        /// </summary>
        /// <param name="dialog">The dialog to start.</param>
        /// <param name="turnContext">The context for the current turn of the conversation.</param>
        /// <param name="accessor">The <see cref="IStatePropertyAccessor{DialogState}"/> accessor
        /// with which to manage the state of the dialog stack.</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 static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor <DialogState> accessor, CancellationToken cancellationToken)
        {
            var dialogSet = new DialogSet(accessor)
            {
                TelemetryClient = dialog.TelemetryClient
            };

            dialogSet.Add(dialog);

            var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false);

            if (turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims))
            {
                // The bot is running as a skill.
                if (turnContext.Activity.Type == ActivityTypes.EndOfConversation && dialogContext.Stack.Any())
                {
                    // Handle remote cancellation request if we have something in the stack.
                    var activeDialogContext = GetActiveDialogContext(dialogContext);

                    var remoteCancelText = "Skill was canceled by a request from the host.";
                    await turnContext.TraceActivityAsync($"{typeof(Dialog).Name}.RunAsync()", label : $"{remoteCancelText}", cancellationToken : cancellationToken).ConfigureAwait(false);

                    // Send cancellation message to the top dialog in the stack to ensure all the parents are canceled in the right order.
                    await activeDialogContext.CancelAllDialogsAsync(true, cancellationToken : cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    // Process a reprompt event sent from the parent.
                    if (turnContext.Activity.Type == ActivityTypes.Event && turnContext.Activity.Name == DialogEvents.RepromptDialog && dialogContext.Stack.Any())
                    {
                        await dialogContext.RepromptDialogAsync(cancellationToken).ConfigureAwait(false);

                        return;
                    }

                    // Run the Dialog with the new message Activity and capture the results so we can send end of conversation if needed.
                    var result = await dialogContext.ContinueDialogAsync(cancellationToken).ConfigureAwait(false);

                    if (result.Status == DialogTurnStatus.Empty)
                    {
                        var startMessageText = $"Starting {dialog.Id}.";
                        await turnContext.TraceActivityAsync($"{typeof(Dialog).Name}.RunAsync()", label : $"{startMessageText}", cancellationToken : cancellationToken).ConfigureAwait(false);

                        result = await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken).ConfigureAwait(false);
                    }

                    // Send end of conversation if it is completed or cancelled.
                    if (result.Status == DialogTurnStatus.Complete || result.Status == DialogTurnStatus.Cancelled)
                    {
                        var endMessageText = $"Dialog {dialog.Id} has **completed**. Sending EndOfConversation.";
                        await turnContext.TraceActivityAsync($"{typeof(Dialog).Name}.RunAsync()", label : $"{endMessageText}", value : result.Result, cancellationToken : cancellationToken).ConfigureAwait(false);

                        // Send End of conversation at the end.
                        var activity = new Activity(ActivityTypes.EndOfConversation)
                        {
                            Value = result.Result
                        };
                        await turnContext.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false);
                    }
                }
            }
Example #30
0
        public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
        {
            turnContext.TurnState.Add <IBot>(this);

            AdaptiveDialog rootDialog = (AdaptiveDialog)this.dialogManager.RootDialog;

            if (turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims))
            {
                rootDialog.AutoEndDialog = true;
            }

            if (this.removeRecipientMention && turnContext?.Activity?.Type == "message")
            {
                turnContext.Activity.RemoveRecipientMention();
            }

            await this.dialogManager.OnTurnAsync(turnContext, cancellationToken : cancellationToken);

            await this.conversationState.SaveChangesAsync(turnContext, false, cancellationToken);

            await this.userState.SaveChangesAsync(turnContext, false, cancellationToken);
        }