/// <summary> /// This method can be called from inside a POST method on any Controller implementation. If the activity is Not an Invoke, and /// DeliveryMode is Not ExpectReplies, and this is NOT a Get request to upgrade to WebSockets, then the activity will be enqueued /// to be processed on a background thread. /// </summary> /// <remarks> /// Note, this is an ImmediateAccept and BackgroundProcessing override of: /// Task IBotFrameworkHttpAdapter.ProcessAsync(HttpRequest httpRequest, HttpResponse httpResponse, IBot bot, CancellationToken cancellationToken = default); /// </remarks> /// <param name="httpRequest">The HTTP request object, typically in a POST handler by a Controller.</param> /// <param name="httpResponse">The HTTP response object.</param> /// <param name="bot">The bot implementation.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive /// notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> async Task IBotFrameworkHttpAdapter.ProcessAsync(HttpRequest httpRequest, HttpResponse httpResponse, IBot bot, CancellationToken cancellationToken = default) { if (httpRequest == null) { throw new ArgumentNullException(nameof(httpRequest)); } if (httpResponse == null) { throw new ArgumentNullException(nameof(httpResponse)); } if (bot == null) { throw new ArgumentNullException(nameof(bot)); } // Get is a socket exchange request, so should be processed by base BotFrameworkHttpAdapter if (httpRequest.Method == HttpMethods.Get) { await base.ProcessAsync(httpRequest, httpResponse, bot, cancellationToken); } else { // Deserialize the incoming Activity var activity = await HttpHelper.ReadRequestAsync <Activity>(httpRequest).ConfigureAwait(false); if (string.IsNullOrEmpty(activity?.Type)) { httpResponse.StatusCode = (int)HttpStatusCode.BadRequest; } else if (activity.Type == ActivityTypes.Invoke || activity.DeliveryMode == DeliveryModes.ExpectReplies) { // NOTE: Invoke and ExpectReplies cannot be performed async, the response must be written before the calling thread is released. await base.ProcessAsync(httpRequest, httpResponse, bot, cancellationToken); } else { // Grab the auth header from the inbound http request var authHeader = httpRequest.Headers["Authorization"]; try { // If authentication passes, queue a work item to process the inbound activity with the bot var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, CredentialProvider, ChannelProvider, HttpClient).ConfigureAwait(false); // Queue the activity to be processed by the ActivityBackgroundService _activityTaskQueue.QueueBackgroundActivity(claimsIdentity, activity); // Activity has been queued to process, so return Ok immediately httpResponse.StatusCode = (int)HttpStatusCode.OK; } catch (UnauthorizedAccessException) { // handle unauthorized here as this layer creates the http response httpResponse.StatusCode = (int)HttpStatusCode.Unauthorized; } } } }
public async Task <IActionResult> Post([FromBody] Activity activity) { // Validate Authorization Header. Should be a jwt token. var authHeader = this.Request.Headers["Authorization"].SingleOrDefault(); try { await JwtTokenValidation.AuthenticateRequest(activity, authHeader, this.credentials); } catch (UnauthorizedAccessException) { return(this.Unauthorized()); } // On message activity, reply with the same text if (activity.Type == ActivityTypes.Message) { var reply = activity.CreateReply($"You said: {activity.Text}"); // Reply to Activity using Connector var connector = new ConnectorClient( new Uri(activity.ServiceUrl, UriKind.Absolute), new MicrosoftAppCredentials(this.credentials.AppId, this.credentials.Password)); await connector.Conversations.ReplyToActivityAsync(reply); } return(this.Ok()); }
/// <summary> /// Creates a turn context and runs the middleware pipeline for an incoming activity. /// </summary> /// <param name="authHeader">The HTTP authentication header of the request.</param> /// <param name="activity">The incoming activity.</param> /// <param name="callback">The code to run at the end of the adapter's middleware /// pipeline.</param> /// <returns>A task that represents the work queued to execute. If the activity type /// was 'Invoke' and the corresponding key (channelId + activityId) was found /// then an InvokeResponse is returned, otherwise null is returned.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="activity"/> is <c>null</c>.</exception> /// <exception cref="UnauthorizedAccessException"> /// authentication failed.</exception> /// <remarks>Call this method to reactively send a message to a conversation. /// <para>This method registers the following services for the turn.<list type="bullet"> /// <item><see cref="IIdentity"/> (key = "BotIdentity"), a claims identity for the bot.</item> /// <item><see cref="IConnectorClient"/>, the channel connector client to use this turn.</item> /// </list></para> /// </remarks> /// <seealso cref="ContinueConversation(string, ConversationReference, Func{ITurnContext, Task})"/> /// <seealso cref="BotAdapter.RunPipeline(ITurnContext, Func{ITurnContext, Task}, System.Threading.CancellationTokenSource)"/> public async Task <InvokeResponse> ProcessActivity(string authHeader, Activity activity, Func <ITurnContext, Task> callback) { BotAssert.ActivityNotNull(activity); var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, _credentialProvider, _httpClient); using (var context = new TurnContext(this, activity)) { context.Services.Add <IIdentity>("BotIdentity", claimsIdentity); var connectorClient = await this.CreateConnectorClientAsync(activity.ServiceUrl, claimsIdentity); context.Services.Add <IConnectorClient>(connectorClient); await base.RunPipeline(context, callback).ConfigureAwait(false); // Handle Invoke scenarios, which deviate from the request/response model in that // the Bot will return a specific body and return code. if (activity.Type == ActivityTypes.Invoke) { Activity invokeResponse = context.Services.Get <Activity>(InvokeReponseKey); if (invokeResponse == null) { // ToDo: Trace Here throw new InvalidOperationException("Bot failed to return a valid 'invokeResponse' activity."); } else { return((InvokeResponse)invokeResponse.Value); } } // For all non-invoke scenarios, the HTTP layers above don't have to mess // withthe Body and return codes. return(null); } }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, ILogger logger, ExecutionContext context) { logger.LogInformation("Messages function received a request."); ISettings settings = new Settings(logger, context); IClippySetRepository clippySetRepository = new ClippySetRepository(logger, settings); IClippySetIndexer clippySetIndexer = new ClippySetIndexer(logger); ICredentialProvider credentialProvider = new SimpleCredentialProvider(settings.MicrosoftAppId, null); IChannelProvider channelProvider = new SimpleChannelProvider(); Activity activity; try { var authorizationHeader = GetAuthorizationHeader(req); activity = await ParseRequestBody(req); await JwtTokenValidation.AuthenticateRequest(activity, authorizationHeader, credentialProvider, channelProvider); } catch (JsonReaderException e) { logger.LogDebug(e, "JSON parser failed to parse request payload."); return(new BadRequestResult()); } catch (UnauthorizedAccessException e) { logger.LogDebug(e, "Request was not propertly authorized."); return(new UnauthorizedResult()); } if (!activity.IsComposeExtensionQuery()) { logger.LogDebug("Request payload was not a compose extension query."); return(new BadRequestObjectResult($"Clippy only supports compose extension query activity types.")); } var queryValue = JObject.FromObject(activity.Value).ToObject <ComposeExtensionValue>(); var query = queryValue.GetParameterValue(); var clippySet = await clippySetRepository.FetchClippySetAsync(); await clippySetIndexer.IndexClippySetAsync(clippySet); var clippies = await clippySetIndexer.FindClippiesByQuery(query); var result = new ComposeExtensionResponse { ComposeExtensionResult = new ComposeExtensionResult { Type = "result", AttachmentLayout = "grid", Attachments = clippies.Select(clippy => new ClippyComposeExtensionCard(clippy).ToAttachment()).ToArray() } }; return(new OkObjectResult(result)); }
public async Task <IActionResult> BotMessages(string instanceId, int routeCounter) { if (routeCounter > 1) { // Node is gone. Return 404, bot should queue messages until the connection is reestablished. return(NotFound()); } IActionResult result; if (instanceId == _instanceId) { // This is the node with the web socket, send the message var activity = await HttpHelper.ReadRequestAsync <Activity>(Request); var authHeader = Request.Headers["Authorization"]; var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, _credentialProvider, _channelProvider); var resultTcs = new TaskCompletionSource <ResourceResponse>(); if (!_replyBuffer.Post(Tuple.Create(activity, resultTcs))) { return(new InternalServerErrorResult()); } var receiveResponse = await resultTcs.Task; result = Ok(); } else { // The websocket is on another node, route to it result = await RerouteAsync(instanceId, routeCounter); } return(result); }
/// <summary> /// Creates a turn context and runs the middleware pipeline for an incoming activity. /// </summary> /// <param name="authHeader">The HTTP authentication header of the request.</param> /// <param name="activity">The incoming activity.</param> /// <param name="callback">The code to run at the end of the adapter's middleware pipeline.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute. If the activity type /// was 'Invoke' and the corresponding key (channelId + activityId) was found /// then an InvokeResponse is returned, otherwise null is returned.</returns> /// <exception cref="ArgumentNullException"><paramref name="activity"/> is <c>null</c>.</exception> /// <exception cref="UnauthorizedAccessException">authentication failed.</exception> /// <remarks>Call this method to reactively send a message to a conversation. /// If the task completes successfully, then if the activity's <see cref="Activity.Type"/> /// is <see cref="ActivityTypes.Invoke"/> and the corresponding key /// (<see cref="Activity.ChannelId"/> + <see cref="Activity.Id"/>) is found /// then an <see cref="InvokeResponse"/> is returned, otherwise null is returned. /// <para>This method registers the following services for the turn.<list type="bullet"> /// <item><see cref="IIdentity"/> (key = "BotIdentity"), a claims identity for the bot.</item> /// <item><see cref="IConnectorClient"/>, the channel connector client to use this turn.</item> /// </list></para> /// </remarks> /// <seealso cref="ContinueConversationAsync(string, ConversationReference, BotCallbackHandler, CancellationToken)"/> /// <seealso cref="BotAdapter.RunPipelineAsync(ITurnContext, BotCallbackHandler, CancellationToken)"/> public async Task <InvokeResponse> ProcessActivityAsync(string authHeader, Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken) { BotAssert.ActivityNotNull(activity); var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, _credentialProvider, _channelProvider, _httpClient).ConfigureAwait(false); return(await ProcessActivityAsync(claimsIdentity, activity, callback, cancellationToken).ConfigureAwait(false)); }
/// <summary> /// Creates a turn context and runs the middleware pipeline for an incoming activity. /// </summary> /// <param name="authHeader">The HTTP authentication header of the request.</param> /// <param name="activity">The incoming activity.</param> /// <param name="callback">The code to run at the end of the adapter's middleware /// pipeline.</param> /// <returns>A task that represents the work queued to execute. If the activity type /// was 'Invoke' and the corresponding key (channelId + activityId) was found /// then an InvokeResponse is returned, otherwise null is returned.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="activity"/> is <c>null</c>.</exception> /// <exception cref="UnauthorizedAccessException"> /// authentication failed.</exception> /// <remarks>Call this method to reactively send a message to a conversation. /// <para>This method registers the following services for the turn.<list type="bullet"> /// <item><see cref="IIdentity"/> (key = "BotIdentity"), a claims identity for the bot.</item> /// <item><see cref="IConnectorClient"/>, the channel connector client to use this turn.</item> /// </list></para> /// </remarks> /// <seealso cref="ContinueConversation(string, ConversationReference, Func{ITurnContext, Task})"/> /// <seealso cref="BotAdapter.RunPipeline(ITurnContext, Func{ITurnContext, Task})"/> public async Task <InvokeResponse> ProcessActivity(string authHeader, Activity activity, Func <ITurnContext, Task> callback) { BotAssert.ActivityNotNull(activity); var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, _credentialProvider, _httpClient).ConfigureAwait(false); return(await ProcessActivity(claimsIdentity, activity, callback).ConfigureAwait(false)); }
public async Task ProcessActivity(string authHeader, Activity activity, Func <IBotContext, Task> callback) { BotAssert.ActivityNotNull(activity); ClaimsIdentity claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, _credentialProvider, _httpClient); // 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 anonymouse identity provided auth is disabled. string botAppId = GetBotId(claimsIdentity); var context = new BotFrameworkBotContext(botAppId, this, activity); await base.RunPipeline(context, callback).ConfigureAwait(false); }
/// <summary> /// Creates a turn context and runs the middleware pipeline for an incoming activity. /// </summary> /// <param name="authHeader">The HTTP authentication header of the request.</param> /// <param name="activity">The incoming activity.</param> /// <param name="callback">The code to run at the end of the adapter's middleware /// pipeline.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="activity"/> is <c>null</c>.</exception> /// <exception cref="UnauthorizedAccessException"> /// authentication failed.</exception> /// <remarks>Call this method to reactively send a message to a conversation. /// <para>This method registers the following services for the turn.<list type="bullet"> /// <item><see cref="IIdentity"/> (key = "BotIdentity"), a claims identity for the bot.</item> /// <item><see cref="IConnectorClient"/>, the channel connector client to use this turn.</item> /// </list></para> /// </remarks> /// <seealso cref="ContinueConversation(string, ConversationReference, Func{ITurnContext, Task})"/> /// <seealso cref="BotAdapter.RunPipeline(ITurnContext, Func{ITurnContext, Task}, System.Threading.CancellationTokenSource)"/> public async Task ProcessActivity(string authHeader, Activity activity, Func <ITurnContext, Task> callback) { BotAssertSlack.ActivityNotNull(activity); var claimsIdentity = await JwtTokenValidation.AuthenticateRequest(activity, authHeader, _credentialProvider, _httpClient); var context = new TurnContext(this, activity); context.Services.Add <IIdentity>("BotIdentity", claimsIdentity); var connectorClient = await this.CreateConnectorClientAsync(activity.ServiceUrl, claimsIdentity); context.Services.Add <IConnectorClient>(connectorClient); await base.RunPipeline(context, callback).ConfigureAwait(false); }
public async void Channel_MsaHeader_Valid_ServiceUrlShouldBeTrusted() { string header = $"Bearer {await new MicrosoftAppCredentials("2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F").GetTokenAsync()}"; var credentials = new SimpleCredentialProvider("2cd87869-38a0-4182-9251-d056e8f0ac24", ""); await JwtTokenValidation.AuthenticateRequest( new Activity { ServiceUrl = "https://smba.trafficmanager.net/amer-client-ss.msg/" }, header, credentials, emptyClient); Assert.True(MicrosoftAppCredentials.IsTrustedServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/")); }
public async void Channel_AuthenticationDisabled_ServiceUrlShouldNotBeTrusted() { var header = ""; var credentials = new SimpleCredentialProvider(); var claimsPrincipal = await JwtTokenValidation.AuthenticateRequest( new Activity { ServiceUrl = "https://webchat.botframework.com/" }, header, credentials, emptyClient); Assert.False(MicrosoftAppCredentials.IsTrustedServiceUrl("https://webchat.botframework.com/")); }
public async void Channel_AuthenticationDisabled_ShouldBeAnonymous() { var header = ""; var credentials = new SimpleCredentialProvider(); var claimsPrincipal = await JwtTokenValidation.AuthenticateRequest( new Activity { ServiceUrl = "https://webchat.botframework.com/" }, header, credentials, emptyClient); Assert.Equal("anonymous", claimsPrincipal.AuthenticationType); }
public async void Channel_MsaHeader_Valid_ServiceUrlShouldBeTrusted() { var header = "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImI0eXNPV0l0RDEzaVFmTExlQkZYOWxSUER0ayIsInR5cCI6IkpXVCIsIng1dCI6ImI0eXNPV0l0RDEzaVFmTExlQkZYOWxSUER0ayJ9.eyJzZXJ2aWNldXJsIjoiaHR0cHM6Ly9zbWJhLnRyYWZmaWNtYW5hZ2VyLm5ldC9hbWVyLWNsaWVudC1zcy5tc2cvIiwibmJmIjoxNTE5Njk3OTQ0LCJleHAiOjE1MTk3MDE1NDQsImlzcyI6Imh0dHBzOi8vYXBpLmJvdGZyYW1ld29yay5jb20iLCJhdWQiOiI3Zjc0NTEzZS02Zjk2LTRkYmMtYmU5ZC05YTgxZmVhMjJiODgifQ.wjApM-MBhEIHSRHJGmivfpyFg0-SrTFh6Xta2RrKlZT4urACPX7kdZAb6oGOeDIm0NU16BPcpEqtCm9nBPmwoKKRbLCQ4Q3DGcB_LY15VCYfiiAnaevNNcvq7j_Hu-oyTmKOqpjfzu8qMIsjySClf1qZFucUrqzccePtlb63DAVfv-nF3bp-sm-zFG7RBX32cCygBMvpVENBroAq3ANfUQCmixkExcGr5npV3dFihSE0H9ntLMGseBdW7dRe5xOXDIgCtcCJPid-A6Vz-DxWGabyy2mVXLwYYuDxP4L5aruGwJIl_Z2-_MjhrWVszoeCRoOlx9-LNtbdSYGWmXWSbg"; var credentials = new SimpleCredentialProvider("7f74513e-6f96-4dbc-be9d-9a81fea22b88", ""); await JwtTokenValidation.AuthenticateRequest( new Activity { ServiceUrl = "https://smba.trafficmanager.net/amer-client-ss.msg/" }, header, credentials, emptyClient); Assert.True(MicrosoftAppCredentials.IsTrustedServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/")); }
public async void Channel_MsaHeader_Invalid_ServiceUrlShouldNotBeTrusted() { string header = $"Bearer {await new MicrosoftAppCredentials("2cd87869-38a0-4182-9251-d056e8f0ac24", "2.30Vs3VQLKt974F").GetTokenAsync()}"; var credentials = new SimpleCredentialProvider("7f74513e-6f96-4dbc-be9d-9a81fea22b88", ""); await Assert.ThrowsAsync <UnauthorizedAccessException>( async() => await JwtTokenValidation.AuthenticateRequest( new Activity { ServiceUrl = "https://webchat.botframework.com/" }, header, credentials, emptyClient)); Assert.False(MicrosoftAppCredentials.IsTrustedServiceUrl("https://webchat.botframework.com/")); }
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)); }
public async Task <IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, ILogger logger, ExecutionContext context) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } using (var scope = logger.BeginScope($"{nameof(MessagesHttpFunction.Run)}")) { if (req == null) { throw new ArgumentNullException(nameof(req)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } logger.LogInformation("Messages function received a request."); // Use the configured service for tests or create ones to use. ISettings settings = this.settings ?? new Settings(logger, context); IStickerSetRepository stickerSetRepository = this.stickerSetRepository ?? new StickerSetRepository(logger, settings); IStickerSetIndexer stickerSetIndexer = this.stickerSetIndexer ?? new StickerSetIndexer(logger); ICredentialProvider credentialProvider = this.credentialProvider ?? new SimpleCredentialProvider(settings.MicrosoftAppId, null); IChannelProvider channelProvider = this.channelProvider ?? new SimpleChannelProvider(); // Parse the incoming activity and authenticate the request Activity activity; try { var authorizationHeader = GetAuthorizationHeader(req); activity = await ParseRequestBody(req); await JwtTokenValidation.AuthenticateRequest(activity, authorizationHeader, credentialProvider, channelProvider); } catch (JsonReaderException e) { logger.LogDebug(e, "JSON parser failed to parse request payload."); return(new BadRequestResult()); } catch (UnauthorizedAccessException e) { logger.LogDebug(e, "Request was not propertly authorized."); return(new UnauthorizedResult()); } // Log telemetry about the activity try { this.LogActivityTelemetry(activity); } catch (Exception ex) { logger.LogWarning(ex, "Error sending user activity telemetry"); } // Reject all activity types other than those related to messaging extensions if (!activity.IsComposeExtensionQuery()) { logger.LogDebug("Request payload was not a messaging extension query."); return(new BadRequestObjectResult($"App only supports messaging extension query activity types.")); } // Get the query string. We expect exactly 1 parameter, so we take the first parameter, regardless of the name. var skip = 0; var count = 25; var query = string.Empty; if (activity.Value != null) { var queryValue = JObject.FromObject(activity.Value).ToObject <ComposeExtensionValue>(); query = queryValue.GetParameterValue(); if (queryValue?.QueryOptions != null) { skip = queryValue.QueryOptions.Skip; count = queryValue.QueryOptions.Count; } } // Find matching stickers var stickerSet = await stickerSetRepository.FetchStickerSetAsync(); await stickerSetIndexer.IndexStickerSetAsync(stickerSet); var stickers = await stickerSetIndexer.FindStickersByQuery(query, skip, count); var result = new ComposeExtensionResponse { ComposeExtensionResult = new ComposeExtensionResult { Type = "result", AttachmentLayout = "grid", Attachments = stickers.Select(sticker => new StickerComposeExtensionCard(sticker).ToAttachment()).ToArray() } }; return(new OkObjectResult(result)); } }