/// <summary>
        /// Determines if a given Auth header is from the Bot Framework Emulator.
        /// </summary>
        /// <param name="authHeader">Bearer Token, in the "Bearer [Long String]" Format.</param>
        /// <returns>True, if the token was issued by the Emulator. Otherwise, false.</returns>
        public static bool IsTokenFromEmulator(string authHeader)
        {
            if (!JwtTokenValidation.IsValidTokenFormat(authHeader))
            {
                return(false);
            }

            // We know is a valid token, split it and work with it:
            // [0] = "Bearer"
            // [1] = "[Big Long String]"
            var bearerToken = authHeader.Split(' ')[1];

            // Parse the Big Long String into an actual token.
            var token = new JwtSecurityToken(bearerToken);

            // Is there an Issuer?
            if (string.IsNullOrWhiteSpace(token.Issuer))
            {
                // No Issuer, means it's not from the Emulator.
                return(false);
            }

            // Is the token issues by a source we consider to be the emulator?
            if (!ToBotFromEmulatorTokenValidationParameters.ValidIssuers.Contains(token.Issuer))
            {
                // Not a Valid Issuer. This is NOT a Bot Framework Emulator Token.
                return(false);
            }

            // The Token is from the Bot Framework Emulator. Success!
            return(true);
        }
Exemple #2
0
        /// <summary>
        /// Checks if the given list of claims represents a skill.
        /// </summary>
        /// <remarks>
        /// A skill claim should contain:
        ///     An <see cref="AuthenticationConstants.VersionClaim"/> claim.
        ///     An <see cref="AuthenticationConstants.AudienceClaim"/> claim.
        ///     An <see cref="AuthenticationConstants.AppIdClaim"/> claim (v1) or an a <see cref="AuthenticationConstants.AuthorizedParty"/> claim (v2).
        /// And the appId claim should be different than the audience claim.
        /// When a channel (webchat, teams, etc.) invokes a bot, the <see cref="AuthenticationConstants.AudienceClaim"/>
        /// is set to <see cref="AuthenticationConstants.ToBotFromChannelTokenIssuer"/> but when a bot calls another bot,
        /// the audience claim is set to the appId of the bot being invoked.
        /// The protocol supports v1 and v2 tokens:
        /// For v1 tokens, the  <see cref="AuthenticationConstants.AppIdClaim"/> is present and set to the app Id of the calling bot.
        /// For v2 tokens, the  <see cref="AuthenticationConstants.AuthorizedParty"/> is present and set to the app Id of the calling bot.
        /// </remarks>
        /// <param name="claims">A list of claims.</param>
        /// <returns>True if the list of claims is a skill claim, false if is not.</returns>
        public static bool IsSkillClaim(IEnumerable <Claim> claims)
        {
            var claimsList = claims.ToList();

            if (claimsList.Any(c => c.Value == AuthenticationConstants.AnonymousSkillAppId && c.Type == AuthenticationConstants.AppIdClaim))
            {
                return(true);
            }

            var version = claimsList.FirstOrDefault(claim => claim.Type == AuthenticationConstants.VersionClaim);

            if (string.IsNullOrWhiteSpace(version?.Value))
            {
                // Must have a version claim.
                return(false);
            }

            var audience = claimsList.FirstOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim)?.Value;

            if (string.IsNullOrWhiteSpace(audience) || AuthenticationConstants.ToBotFromChannelTokenIssuer.Equals(audience, StringComparison.OrdinalIgnoreCase))
            {
                // The audience is https://api.botframework.com and not an appId.
                return(false);
            }

            var appId = JwtTokenValidation.GetAppIdFromClaims(claimsList);

            if (string.IsNullOrWhiteSpace(appId))
            {
                return(false);
            }

            // Skill claims must contain and app ID and the AppID must be different than the audience.
            return(appId != audience);
        }
        /// <inheritdoc/>
        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 configured for this bot.
                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);
        }
Exemple #4
0
        public override async Task <AuthenticateRequestResult> AuthenticateRequestAsync(Activity activity, string authHeader, CancellationToken cancellationToken)
        {
            var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, new DelegatingCredentialProvider(_credentialFactory), GetChannelProvider(), _authConfiguration, _authHttpClient).ConfigureAwait(false);

            var outboundAudience = SkillValidation.IsSkillClaim(claimsIdentity.Claims) ? JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims) : _toChannelFromBotOAuthScope;

            var callerId = await GenerateCallerIdAsync(_credentialFactory, claimsIdentity, _callerId, cancellationToken).ConfigureAwait(false);

            var connectorFactory = new ConnectorFactoryImpl(GetAppId(claimsIdentity), _toChannelFromBotOAuthScope, _loginEndpoint, true, _credentialFactory, _httpClientFactory, _logger);

            return(new AuthenticateRequestResult {
                ClaimsIdentity = claimsIdentity, Audience = outboundAudience, CallerId = callerId, ConnectorFactory = connectorFactory
            });
        }
Exemple #5
0
        internal static async Task ValidateIdentity(ClaimsIdentity identity, ICredentialProvider credentials)
        {
            if (identity == null)
            {
                // No valid identity. Not Authorized.
                throw new UnauthorizedAccessException("Invalid Identity");
            }

            if (!identity.IsAuthenticated)
            {
                // The token is in some way invalid. Not Authorized.
                throw new UnauthorizedAccessException("Token Not Authenticated");
            }

            var versionClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.VersionClaim);

            if (versionClaim == null)
            {
                // No version claim
                throw new UnauthorizedAccessException($"'{AuthenticationConstants.VersionClaim}' claim is required on skill Tokens.");
            }

            // Look for the "aud" claim, but only if issued from the Bot Framework
            var audienceClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.AudienceClaim)?.Value;

            if (string.IsNullOrWhiteSpace(audienceClaim))
            {
                // Claim is not present or doesn't have a value. Not Authorized.
                throw new UnauthorizedAccessException($"'{AuthenticationConstants.AudienceClaim}' claim is required on skill Tokens.");
            }

            if (!await credentials.IsValidAppIdAsync(audienceClaim).ConfigureAwait(false))
            {
                // The AppId is not valid. Not Authorized.
                throw new UnauthorizedAccessException($"Invalid audience.");
            }

            var appId = JwtTokenValidation.GetAppIdFromClaims(identity.Claims);

            if (string.IsNullOrWhiteSpace(appId))
            {
                // Invalid appId
                throw new UnauthorizedAccessException($"Invalid appId.");
            }

            // TODO: check the appId against the registered skill client IDs.
            // Check the AppId and ensure that only works against my whitelist authConfig can have info on how to get the
            // whitelist AuthenticationConfiguration
            // We may need to add a ClaimsIdentityValidator delegate or class that allows the dev to inject a custom validator.
        }
        /// <summary>
        /// Generates the appropriate callerId to write onto the activity, this might be null.
        /// </summary>
        /// <param name="credentialFactory">A <see cref="ServiceClientCredentialsFactory"/> to use.</param>
        /// <param name="claimsIdentity">The inbound claims.</param>
        /// <param name="callerId">The default callerId to use if this is not a skill.</param>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <returns>The callerId, this might be null.</returns>
        protected internal async Task <string> GenerateCallerIdAsync(ServiceClientCredentialsFactory credentialFactory, ClaimsIdentity claimsIdentity, string callerId, CancellationToken cancellationToken)
        {
            // Is the bot accepting all incoming messages?
            if (await credentialFactory.IsAuthenticationDisabledAsync(cancellationToken).ConfigureAwait(false))
            {
                // Return null so that the callerId is cleared.
                return(null);
            }

            // Is the activity from another bot?
            return(SkillValidation.IsSkillClaim(claimsIdentity.Claims)
                ? $"{CallerIdConstants.BotToBotPrefix}{JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims)}"
                : callerId);
        }
        public override async Task <AuthenticateRequestResult> AuthenticateRequestAsync(Activity activity, string authHeader, CancellationToken cancellationToken)
        {
            var claimsIdentity = await JwtTokenValidation_AuthenticateRequestAsync(activity, authHeader, _credentialFactory, _authConfiguration, _httpClient, cancellationToken).ConfigureAwait(false);

            var scope = SkillValidation.IsSkillClaim(claimsIdentity.Claims) ? JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims) : _toChannelFromBotOAuthScope;

            var callerId = await GenerateCallerIdAsync(_credentialFactory, claimsIdentity, cancellationToken).ConfigureAwait(false);

            var connectorFactory = new ConnectorFactoryImpl(BuiltinBotFrameworkAuthentication.GetAppId(claimsIdentity), _toChannelFromBotOAuthScope, _toChannelFromBotLoginUrl, _validateAuthority, _credentialFactory, _httpClient, _logger);

            return(new AuthenticateRequestResult {
                ClaimsIdentity = claimsIdentity, Scope = scope, CallerId = callerId, ConnectorFactory = connectorFactory
            });
        }
Exemple #8
0
        public override async Task <AuthenticateRequestResult> AuthenticateRequestAsync(Activity activity, string authHeader, CancellationToken cancellationToken)
        {
            var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, new DelegatingCredentialProvider(_credentialFactory), GetChannelProvider(), _authConfiguration, _httpClient).ConfigureAwait(false);

            var scope = SkillValidation.IsSkillClaim(claimsIdentity.Claims) ? JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims) : _toChannelFromBotOAuthScope;

            var callerId = await GenerateCallerIdAsync(_credentialFactory, claimsIdentity, cancellationToken).ConfigureAwait(false);

            var appId = GetAppId(claimsIdentity);

            var credentials = await _credentialFactory.CreateCredentialsAsync(appId, scope, _loginEndpoint, true, cancellationToken).ConfigureAwait(false);

            return(new AuthenticateRequestResult {
                ClaimsIdentity = claimsIdentity, Credentials = credentials, Scope = scope, CallerId = callerId
            });
        }
Exemple #9
0
        /// <summary>
        /// Determines if a given Auth header is from from a skill to bot or bot to skill request.
        /// </summary>
        /// <param name="authHeader">Bearer Token, in the "Bearer [Long String]" Format.</param>
        /// <returns>True, if the token was issued for a skill to bot communication. Otherwise, false.</returns>
        public static bool IsSkillToken(string authHeader)
        {
            if (!JwtTokenValidation.IsValidTokenFormat(authHeader))
            {
                return(false);
            }

            // We know is a valid token, split it and work with it:
            // [0] = "Bearer"
            // [1] = "[Big Long String]"
            var bearerToken = authHeader.Split(' ')[1];

            // Parse the Big Long String into an actual token.
            var token = new JwtSecurityToken(bearerToken);

            return(IsSkillClaim(token.Claims));
        }
Exemple #10
0
        public override async Task <AuthenticateRequestResult> AuthenticateStreamingRequestAsync(string authHeader, string channelIdHeader, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(channelIdHeader) && !await _credentialFactory.IsAuthenticationDisabledAsync(cancellationToken).ConfigureAwait(false))
            {
                throw new UnauthorizedAccessException();
            }

            var claimsIdentity = await JwtTokenValidation.ValidateAuthHeader(authHeader, new DelegatingCredentialProvider(_credentialFactory), GetChannelProvider(), channelIdHeader, httpClient : _authHttpClient).ConfigureAwait(false);

            var outboundAudience = SkillValidation.IsSkillClaim(claimsIdentity.Claims) ? JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims) : _toChannelFromBotOAuthScope;

            var callerId = await GenerateCallerIdAsync(_credentialFactory, claimsIdentity, _callerId, cancellationToken).ConfigureAwait(false);

            return(new AuthenticateRequestResult {
                ClaimsIdentity = claimsIdentity, Audience = outboundAudience, CallerId = callerId
            });
        }
Exemple #11
0
        /// <summary>
        /// Validates the security tokens required by the Bot Framework Protocol. Throws on any exceptions.
        /// </summary>
        /// <param name="activity">The incoming Activity from the Bot Framework or the Emulator</param>
        /// <param name="authHeader">The Bearer token included as part of the request</param>
        /// <param name="credentials">The set of valid credentials, such as the Bot Application ID</param>
        /// <param name="httpClient">Validating an Activity requires validating the claimset on the security token. This
        /// validation may require outbound calls for Endorsement validation and other checks. Those calls are made to
        /// TLS services, which are (latency wise) expensive resources. The httpClient passed in here, if shared by the layers
        /// above from call to call, enables connection reuse which is a significant performance and resource improvement.</param>
        /// <returns>Task tracking operation</returns>
        public static async Task AssertValidActivity(IActivity activity, string authHeader, ICredentialProvider credentials, HttpClient httpClient = null)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                // No auth header was sent. We might be on the anonymous code path.
                bool isAuthDisabled = await credentials.IsAuthenticationDisabledAsync();

                if (isAuthDisabled)
                {
                    // We are on the anonymous code path.
                    return;
                }
            }

            // Go through the standard authentication path.
            await JwtTokenValidation.AuthenticateRequest(activity, authHeader, credentials, httpClient ?? _httpClient);
        }
Exemple #12
0
        internal static async Task ValidateIdentityAsync(ClaimsIdentity identity, ICredentialProvider credentials)
        {
            if (identity == null)
            {
                // No valid identity. Not Authorized.
                throw new UnauthorizedAccessException("Invalid Identity");
            }

            if (!identity.IsAuthenticated)
            {
                // The token is in some way invalid. Not Authorized.
                throw new UnauthorizedAccessException("Token Not Authenticated");
            }

            var versionClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.VersionClaim);

            if (versionClaim == null)
            {
                // No version claim
                throw new UnauthorizedAccessException($"'{AuthenticationConstants.VersionClaim}' claim is required on skill Tokens.");
            }

            // Look for the "aud" claim, but only if issued from the Bot Framework
            var audienceClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.AudienceClaim)?.Value;

            if (string.IsNullOrWhiteSpace(audienceClaim))
            {
                // Claim is not present or doesn't have a value. Not Authorized.
                throw new UnauthorizedAccessException($"'{AuthenticationConstants.AudienceClaim}' claim is required on skill Tokens.");
            }

            if (!await credentials.IsValidAppIdAsync(audienceClaim).ConfigureAwait(false))
            {
                // The AppId is not valid. Not Authorized.
                throw new UnauthorizedAccessException("Invalid audience.");
            }

            var appId = JwtTokenValidation.GetAppIdFromClaims(identity.Claims);

            if (string.IsNullOrWhiteSpace(appId))
            {
                // Invalid appId
                throw new UnauthorizedAccessException("Invalid appId.");
            }
        }
        /// <summary>
        /// Validates the security tokens required by the Bot Framework Protocol. Throws on any exceptions. 
        /// </summary>
        /// <param name="activity">The incoming Activity from the Bot Framework or the Emulator</param>
        /// <param name="authHeader">The Bearer token included as part of the request</param>
        /// <param name="credentials">The set of valid credentials, such as the Bot Application ID</param>
        /// <param name="httpClient">Validating an Activity requires validating the claimset on the security token. This 
        /// validation may require outbound calls for Endorsement validation and other checks. Those calls are made to
        /// TLS services, which are (latency wise) expensive resources. The httpClient passed in here, if shared by the layers
        /// above from call to call, enables connection reuse which is a significant performance and resource improvement.</param>
        /// <returns>Nothing</returns>
        public static async Task AssertValidActivity(Activity activity, string authHeader, ICredentialProvider credentials, HttpClient httpClient)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                // No auth header was sent. We might be on the anonymous code path. 
                bool isAuthDisabled = await credentials.IsAuthenticationDisabledAsync();
                if (isAuthDisabled)
                {
                    // We are on the anonymous code path. 
                    return;
                }
            }

            // Go through the standard authentication path. 
            await JwtTokenValidation.ValidateAuthHeader(authHeader, credentials, activity.ServiceUrl, httpClient);

            // On the standard Auth path, we need to trust the URL that was incoming. 
            MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
        }
        public override async Task <ClaimsIdentity> AuthenticateChannelRequestAsync(string authHeader, CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(authHeader))
            {
                var isAuthDisabled = await new DelegatingCredentialProvider(_credentialsFactory).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());
            }

            return(await JwtTokenValidation
                   .ValidateAuthHeader(authHeader, new DelegatingCredentialProvider(_credentialsFactory), GetChannelProvider(), "unknown", _authConfiguration)
                   .ConfigureAwait(false));
        }
        /// <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 the configured set of allowedCallers.");
                }
            }

            return(Task.CompletedTask);
        }