/// <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(); 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); }
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."); }
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).ToUpperInvariant(); 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); }
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> /// 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) { if (claims == null) { throw new ArgumentNullException(nameof(claims)); } if (!claims.Any()) { throw new UnauthorizedAccessException("SkillValidation.IsSkillClaim.claims parameter must contain at least one element."); } var claimsList = claims.ToList(); 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); }
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) { var authorizationHeader = actionContext.Request.Headers.Authorization; if (authorizationHeader != null && SkillValidation.IsSkillToken(authorizationHeader.ToString())) { var activities = base.GetActivities(actionContext); if (activities.Any()) { var authConfiguration = this.GetAuthenticationConfiguration(); var credentialProvider = this.GetCredentialProvider(); try { foreach (var activity in activities) { var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authorizationHeader.ToString(), credentialProvider, authConfiguration, _httpClient).ConfigureAwait(false); // this is done in JwtTokenValidation.AuthenticateRequest, but the oauthScope is not set so we update it here MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl, oauthScope: JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims)); } } catch (UnauthorizedAccessException) { actionContext.Response = BotAuthenticator.GenerateUnauthorizedResponse(actionContext.Request, "BotAuthenticator failed to authenticate incoming request!"); return; } await base.ContinueOnActionExecutingAsync(actionContext, cancellationToken); return; } } await base.OnActionExecutingAsync(actionContext, cancellationToken); }