Example #1
0
        private async Task <bool> DeduplicatedTokenExchangeIdAsync(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            // Create a StoreItem with Etag of the unique 'signin/tokenExchange' request
            var storeItem = new TokenStoreItem
            {
                ETag = (turnContext.Activity.Value as JObject).Value <string>("id")
            };

            var storeItems = new Dictionary <string, object> {
                { TokenStoreItem.GetStorageKey(turnContext), storeItem }
            };

            try
            {
                // Writing the IStoreItem with ETag of unique id will succeed only once
                await _storage.WriteAsync(storeItems, cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)

                // Memory storage throws a generic exception with a Message of 'Etag conflict. [other error info]'
                // CosmosDbPartitionedStorage throws: ex.Message.Contains("pre-condition is not met")
                when(ex.Message.StartsWith("Etag conflict", StringComparison.OrdinalIgnoreCase) || ex.Message.Contains("pre-condition is not met"))
                {
                    // Do NOT proceed processing this message, some other thread or machine already has processed it.

                    // Send 200 invoke response.
                    await SendInvokeResponseAsync(turnContext, cancellationToken : cancellationToken).ConfigureAwait(false);

                    return(false);
                }

            return(true);
        }
Example #2
0
        /// <summary>
        /// Determines if a "signin/tokenExchange" should be processed by this caller.
        ///
        /// If a token exchange is unsuccessful, an InvokeResponse of PreconditionFailed is sent.
        /// </summary>
        /// <param name="turnContext"><see cref="ITurnContext"/> for this specific activity.</param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/> for this specific process.</param>
        /// <returns>True if the bot should continue processing this TokenExchange request.</returns>
        public async Task <bool> ShouldProcessTokenExchange(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            if (turnContext.Activity.Name != SignInConstants.TokenExchangeOperationName)
            {
                throw new InvalidOperationException("Only 'signin/tokenExchange' invoke activities can be procssed by TokenExchangeHelper.");
            }

            if (!await this.ExchangedTokenAsync(turnContext, cancellationToken).ConfigureAwait(false))
            {
                // If the TokenExchange is NOT successful, the response will have already been sent by ExchangedTokenAsync
                return(false);
            }

            // If a user is signed into multiple Teams clients, the Bot might receive a "signin/tokenExchange" from each client.
            // Each token exchange request for a specific user login will have an identical Activity.Value.Id.
            // Only one of these token exchange requests should be processe by the bot.  For a distributed bot in production,
            // this requires a distributed storage to ensure only one token exchange is processed.

            // This example utilizes Bot Framework IStorage's ETag implementation for token exchange activity deduplication.

            // Create a StoreItem with Etag of the unique 'signin/tokenExchange' request
            var storeItem = new TokenStoreItem
            {
                ETag = (turnContext.Activity.Value as JObject).Value <string>("id")
            };

            var storeItems = new Dictionary <string, object> {
                { TokenStoreItem.GetStorageKey(turnContext), storeItem }
            };

            try
            {
                // Writing the IStoreItem with ETag of unique id will succeed only once
                await _storage.WriteAsync(storeItems, cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
                // Memory storage throws a generic exception with a Message of 'Etag conflict. [other error info]'
                when(ex.Message.StartsWith("Etag conflict"))
                {
                    // CosmosDbPartitionedStorage throws: ex.Message.Contains("pre-condition is not met")


                    // Do NOT send this message on to be processed, some other thread or machine already has processed it.

                    // TODO: Should send 200 invoke response here???
                    return(false);
                }

            return(true);
        }