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); }
/// <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)); }
/// <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."); }
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)); }
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); }
/// <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); }
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)); }
/// <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, }, }); }
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); }
private static bool IsSkillBot(ITurnContext turnContext) { return(turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims)); }
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, }, }); }
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)); }
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); } } }
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); }