/// <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)); }
private async Task <ResourceResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string replyToActivityId, Activity activity, CancellationToken cancellationToken) { SkillConversationReference skillConversationReference; try { skillConversationReference = await _conversationIdFactory.GetSkillConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false); } catch (NotImplementedException) { // Attempt to get SkillConversationReference using deprecated method. // this catch should be removed once we remove the deprecated method. // We need to use the deprecated method for backward compatibility. #pragma warning disable 618 var conversationReference = await _conversationIdFactory.GetConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false); #pragma warning restore 618 skillConversationReference = new SkillConversationReference { ConversationReference = conversationReference, OAuthScope = ChannelProvider != null && ChannelProvider.IsGovernment() ? GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope : AuthenticationConstants.ToChannelFromBotOAuthScope }; } if (skillConversationReference == null) { throw new KeyNotFoundException(); } ResourceResponse resourceResponse = null; var callback = new BotCallbackHandler(async(turnContext, ct) => { turnContext.TurnState.Add(SkillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = replyToActivityId; turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims)}"; switch (activity.Type) { case ActivityTypes.EndOfConversation: await _conversationIdFactory.DeleteConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false); ApplyEoCToTurnContextActivity(turnContext, activity); await _bot.OnTurnAsync(turnContext, ct).ConfigureAwait(false); break; case ActivityTypes.Event: ApplyEventToTurnContextActivity(turnContext, activity); await _bot.OnTurnAsync(turnContext, ct).ConfigureAwait(false); break; default: resourceResponse = await turnContext.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false); break; } }); await _adapter.ContinueConversationAsync(claimsIdentity, skillConversationReference.ConversationReference, skillConversationReference.OAuthScope, callback, cancellationToken).ConfigureAwait(false); return(resourceResponse ?? new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))); }
public void CreateAnonymousSkillClaimTest() { var sut = SkillValidation.CreateAnonymousSkillClaim(); Assert.Equal(AuthenticationConstants.AnonymousSkillAppId, JwtTokenValidation.GetAppIdFromClaims(sut.Claims)); Assert.Equal(AuthenticationConstants.AnonymousAuthType, sut.AuthenticationType); }
/// <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."); }
private async Task <ResourceResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string replyToActivityId, Activity activity, CancellationToken cancellationToken) { var skillConversationReference = await GetSkillConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false); ResourceResponse resourceResponse = null; var callback = new BotCallbackHandler(async(turnContext, ct) => { turnContext.TurnState.Add(SkillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = replyToActivityId; turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims)}"; switch (activity.Type) { case ActivityTypes.EndOfConversation: await _conversationIdFactory.DeleteConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false); ApplyEoCToTurnContextActivity(turnContext, activity); await _bot.OnTurnAsync(turnContext, ct).ConfigureAwait(false); break; case ActivityTypes.Event: ApplyEventToTurnContextActivity(turnContext, activity); await _bot.OnTurnAsync(turnContext, ct).ConfigureAwait(false); break; default: resourceResponse = await turnContext.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false); break; } }); await _adapter.ContinueConversationAsync(claimsIdentity, skillConversationReference.ConversationReference, skillConversationReference.OAuthScope, callback, cancellationToken).ConfigureAwait(false); return(resourceResponse ?? new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))); }
public void GetAppIdFromClaimsTests() { var v1Claims = new List <Claim>(); var v2Claims = new List <Claim>(); var appId = Guid.NewGuid().ToString(); // Empty list Assert.Null(JwtTokenValidation.GetAppIdFromClaims(v1Claims)); // AppId there but no version (assumes v1) v1Claims.Add(new Claim(AuthenticationConstants.AppIdClaim, appId)); Assert.Equal(appId, JwtTokenValidation.GetAppIdFromClaims(v1Claims)); // AppId there with v1 version v1Claims.Add(new Claim(AuthenticationConstants.VersionClaim, "1.0")); Assert.Equal(appId, JwtTokenValidation.GetAppIdFromClaims(v1Claims)); // v2 version but no azp v2Claims.Add(new Claim(AuthenticationConstants.VersionClaim, "2.0")); Assert.Null(JwtTokenValidation.GetAppIdFromClaims(v2Claims)); // v2 version with azp v2Claims.Add(new Claim(AuthenticationConstants.AuthorizedParty, appId)); Assert.Equal(appId, JwtTokenValidation.GetAppIdFromClaims(v2Claims)); }
public async Task AuthenticateSetsAnonymousSkillClaim() { var sut = new TestChannelServiceHandler(); await sut.HandleReplyToActivityAsync(null, "123", "456", new Activity(), CancellationToken.None); Assert.Equal(AuthenticationConstants.AnonymousAuthType, sut.ClaimsIdentity.AuthenticationType); Assert.Equal(AuthenticationConstants.AnonymousSkillAppId, JwtTokenValidation.GetAppIdFromClaims(sut.ClaimsIdentity.Claims)); }
private BotFrameworkSkill GetCallingSkill(ClaimsIdentity claimsIdentity) { var appId = JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims); if (string.IsNullOrWhiteSpace(appId)) { return(null); } return(_skillsConfig.Skills.Values.FirstOrDefault(s => string.Equals(s.AppId, appId, StringComparison.InvariantCultureIgnoreCase))); }
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> /// 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); }
/// <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); }
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); }
private async Task SendToSkill(ITurnContext turnContext, BotFrameworkSkill targetSkill, CancellationToken cancellationToken) { // NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill // will have access to current accurate state. await _conversationState.SaveChangesAsync(turnContext, force : true, cancellationToken : cancellationToken); // route the activity to the skill InvokeResponse response; if (turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims)) { // Check that the appId claim in the skill request is in the list of skills configured for this bot. var appId = JwtTokenValidation.GetAppIdFromClaims(claimIdentity.Claims); response = await _skillClient.PostActivityAsync <InvokeResponse>(appId, _botId, targetSkill, _skillsConfig.SkillHostEndpoint, turnContext.Activity, cancellationToken); }
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); }
private async Task <ResourceResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string replyToActivityId, Activity activity, CancellationToken cancellationToken) { var skillConversationReference = await GetSkillConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false); ResourceResponse resourceResponse = null; var callback = new BotCallbackHandler(async(turnContext, ct) => { turnContext.TurnState.Add(_skillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = replyToActivityId; turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims)}"; switch (activity.Type) { case ActivityTypes.EndOfConversation: await _conversationIdFactory.DeleteConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false); await SendToBotAsync(activity, turnContext, ct).ConfigureAwait(false); break; case ActivityTypes.Event: await SendToBotAsync(activity, turnContext, ct).ConfigureAwait(false); break; case ActivityTypes.Command: case ActivityTypes.CommandResult: if (activity.Name.StartsWith("application/", StringComparison.Ordinal)) { // Send to channel and capture the resource response for the SendActivityCall so we can return it. resourceResponse = await turnContext.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false); } else { await SendToBotAsync(activity, turnContext, ct).ConfigureAwait(false); } break; default: // Capture the resource response for the SendActivityCall so we can return it. resourceResponse = await turnContext.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false); break; } }); await _adapter.ContinueConversationAsync(claimsIdentity, skillConversationReference.ConversationReference, skillConversationReference.OAuthScope, callback, cancellationToken).ConfigureAwait(false); return(resourceResponse ?? new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))); }
/// <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); }
internal async Task <ResourceResponse> OnUpdateActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default) { var skillConversationReference = await GetSkillConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false); ResourceResponse resourceResponse = null; var callback = new BotCallbackHandler(async(turnContext, ct) => { turnContext.TurnState.Add(_skillConversationReferenceKey, skillConversationReference); activity.ApplyConversationReference(skillConversationReference.ConversationReference); turnContext.Activity.Id = activityId; turnContext.Activity.CallerId = $"{CallerIdConstants.BotToBotPrefix}{JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims)}"; resourceResponse = await turnContext.UpdateActivityAsync(activity, cancellationToken).ConfigureAwait(false); }); await _adapter.ContinueConversationAsync(claimsIdentity, skillConversationReference.ConversationReference, skillConversationReference.OAuthScope, callback, cancellationToken).ConfigureAwait(false); return(resourceResponse ?? new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))); }
/// <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); }
public override async Task <InvokeResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken) { if (!claimsIdentity.Claims.Any(x => x.Type == "azp")) { if (_conversationMap.TryGetValue(activity.Conversation.Id, out string appId)) { claimsIdentity.AddClaim(new Claim("azp", appId)); claimsIdentity.AddClaim(new Claim("ver", "2.0")); } } else { var appId = JwtTokenValidation.GetAppIdFromClaims(claimsIdentity.Claims); _conversationMap.AddOrUpdate(activity.Conversation.Id, appId, (id, a) => appId); } if (activity.Type == ActivityTypes.EndOfConversation) { _conversationMap.TryRemove(activity.Conversation.Id, out var conversation); } return(await base.ProcessActivityAsync(claimsIdentity, activity, callback, cancellationToken).ConfigureAwait(false)); }
public async void Channel_AuthenticationDisabledAndSkill_ShouldBeAnonymous() { var header = string.Empty; var credentials = new SimpleCredentialProvider(); var claimsPrincipal = await JwtTokenValidation.AuthenticateRequest( new Activity { ChannelId = Channels.Emulator, ServiceUrl = "https://webchat.botframework.com/", RelatesTo = new ConversationReference(), Recipient = new ChannelAccount { Role = RoleTypes.Skill } }, header, credentials, new SimpleChannelProvider(), emptyClient); Assert.Equal(AuthenticationConstants.AnonymousAuthType, claimsPrincipal.AuthenticationType); Assert.Equal(AuthenticationConstants.AnonymousSkillAppId, JwtTokenValidation.GetAppIdFromClaims(claimsPrincipal.Claims)); }
/// <inheritdoc/> public override async Task <DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken)) { if (options is CancellationToken) { throw new ArgumentException($"{nameof(options)} cannot be a cancellation token"); } if (Disabled != null && Disabled.GetValue(dc.State)) { return(await dc.EndDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false)); } if (dc.Context.Activity.ChannelId != Channels.Msteams) { throw new InvalidOperationException($"{Kind} works only on the Teams channel."); } IActivity activity = null; if (Activity != null) { activity = await Activity.BindAsync(dc, dc.State).ConfigureAwait(false); } string teamsChannelId = TeamsChannelId.GetValueOrNull(dc.State); if (string.IsNullOrEmpty(teamsChannelId)) { teamsChannelId = dc.Context.Activity.TeamsGetChannelId(); } Tuple <ConversationReference, string> result; // Check for legacy adapter if (dc.Context.Adapter is BotFrameworkAdapter) { // TeamsInfo.SendMessageToTeamsChannelAsync requires AppCredentials var credentials = dc.Context.TurnState.Get <IConnectorClient>()?.Credentials as MicrosoftAppCredentials; if (credentials == null) { throw new InvalidOperationException($"Missing credentials as {nameof(MicrosoftAppCredentials)} in {nameof(IConnectorClient)} from TurnState"); } // The result comes back as a tuple, which is used to set the two properties (if present). result = await TeamsInfo.SendMessageToTeamsChannelAsync(dc.Context, activity, teamsChannelId, credentials, cancellationToken : cancellationToken).ConfigureAwait(false); } else if (dc.Context.Adapter is CloudAdapterBase) { // Retrieve the bot appid from TurnState's ClaimsIdentity string appId; if (dc.Context.TurnState.Get <ClaimsIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity botIdentity) { // Apparently 'version' is sometimes empty, which will result in no id returned from GetAppIdFromClaims appId = JwtTokenValidation.GetAppIdFromClaims(botIdentity.Claims); if (string.IsNullOrEmpty(appId)) { appId = botIdentity.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim)?.Value; } if (string.IsNullOrEmpty(appId)) { throw new InvalidOperationException($"Missing AppIdClaim in ClaimsIdentity."); } } else { throw new InvalidOperationException($"Missing {BotAdapter.BotIdentityKey} in {nameof(ITurnContext)} TurnState."); } // The result comes back as a tuple, which is used to set the two properties (if present). result = await TeamsInfo.SendMessageToTeamsChannelAsync(dc.Context, activity, teamsChannelId, appId, cancellationToken : cancellationToken).ConfigureAwait(false); } else { throw new InvalidOperationException($"The adapter does not support {nameof(SendMessageToTeamsChannel)}."); } if (ConversationReferenceProperty != null) { dc.State.SetValue(ConversationReferenceProperty.GetValue(dc.State), result.Item1); } if (ActivityIdProperty != null) { dc.State.SetValue(ActivityIdProperty.GetValue(dc.State), result.Item2); } return(await dc.EndDialogAsync(result, cancellationToken : cancellationToken).ConfigureAwait(false)); }