Exemplo n.º 1
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));
        }
Exemplo n.º 2
0
        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)));
        }
Exemplo n.º 3
0
        public void CreateAnonymousSkillClaimTest()
        {
            var sut = SkillValidation.CreateAnonymousSkillClaim();

            Assert.Equal(AuthenticationConstants.AnonymousSkillAppId, JwtTokenValidation.GetAppIdFromClaims(sut.Claims));
            Assert.Equal(AuthenticationConstants.AnonymousAuthType, sut.AuthenticationType);
        }
Exemplo n.º 4
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.");
        }
Exemplo n.º 5
0
        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)));
        }
Exemplo n.º 9
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>
        /// 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);
        }
Exemplo n.º 11
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);
        }
Exemplo n.º 12
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);
        }
        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);
            }
Exemplo n.º 14
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);
        }
Exemplo n.º 15
0
        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);
        }
Exemplo n.º 17
0
        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)));
        }
Exemplo n.º 18
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);
        }
Exemplo n.º 19
0
        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));
        }