/// <summary> /// Accept an incoming webhook request and convert it into a TurnContext which can be processed by the bot's logic. /// </summary> /// <param name="request">The incoming HTTP request.</param> /// <param name="response">When this method completes, the HTTP response to send.</param> /// <param name="bot">The bot that will handle the incoming activity.</param> /// <param name="cancellationToken">A cancellation token for the task.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public async Task ProcessAsync(HttpRequest request, HttpResponse response, IBot bot, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } if (bot == null) { throw new ArgumentNullException(nameof(bot)); } string body; Activity activity = null; using (var sr = new StreamReader(request.Body)) { body = await sr.ReadToEndAsync().ConfigureAwait(false); } if (!_slackClient.VerifySignature(request, body)) { const string text = "Rejected due to mismatched header signature"; await SlackHelper.WriteAsync(response, HttpStatusCode.Unauthorized, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); throw new Exception(text); } var requestContentType = request.Headers["Content-Type"].ToString(); if (requestContentType == "application/x-www-form-urlencoded") { var postValues = SlackHelper.QueryStringToDictionary(body); if (postValues.ContainsKey("payload")) { var payload = JsonConvert.DeserializeObject <InteractionPayload>(postValues["payload"]); activity = SlackHelper.PayloadToActivity(payload); } else if (postValues.ContainsKey("command")) { var serializedPayload = JsonConvert.SerializeObject(postValues); var payload = JsonConvert.DeserializeObject <CommandPayload>(serializedPayload); activity = await SlackHelper.CommandToActivityAsync(payload, _slackClient, cancellationToken).ConfigureAwait(false); } } else if (requestContentType == "application/json") { var bodyObject = JObject.Parse(body); if (bodyObject["type"]?.ToString() == "url_verification") { var verificationEvent = bodyObject.ToObject <UrlVerificationEvent>(); await SlackHelper.WriteAsync(response, HttpStatusCode.OK, verificationEvent.Challenge, Encoding.UTF8, cancellationToken).ConfigureAwait(false); return; } if (!string.IsNullOrWhiteSpace(_slackClient.Options.SlackVerificationToken) && bodyObject["token"]?.ToString() != _slackClient.Options.SlackVerificationToken) { var text = $"Rejected due to mismatched verificationToken:{bodyObject["token"]}"; await SlackHelper.WriteAsync(response, HttpStatusCode.Forbidden, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); throw new Exception(text); } if (bodyObject["type"]?.ToString() == "event_callback") { // this is an event api post var eventRequest = bodyObject.ToObject <EventRequest>(); activity = await SlackHelper.EventToActivityAsync(eventRequest, _slackClient, cancellationToken).ConfigureAwait(false); } } // As per official Slack API docs, some additional request types may be receieved that can be ignored // but we should respond with a 200 status code // https://api.slack.com/interactivity/slash-commands if (activity == null) { await SlackHelper.WriteAsync(response, HttpStatusCode.OK, "Unable to transform request / payload into Activity. Possible unrecognized request type", Encoding.UTF8, cancellationToken).ConfigureAwait(false); } else { using (var context = new TurnContext(this, activity)) { context.TurnState.Add("httpStatus", ((int)HttpStatusCode.OK).ToString(System.Globalization.CultureInfo.InvariantCulture)); await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false); var code = Convert.ToInt32(context.TurnState.Get <string>("httpStatus"), System.Globalization.CultureInfo.InvariantCulture); var statusCode = (HttpStatusCode)code; var text = context.TurnState.Get <object>("httpBody") != null?context.TurnState.Get <object>("httpBody").ToString() : string.Empty; await SlackHelper.WriteAsync(response, statusCode, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); } } }
/// <summary> /// Accept an incoming webhook request and convert it into a TurnContext which can be processed by the bot's logic. /// </summary> /// <param name="request">The incoming HTTP request.</param> /// <param name="response">When this method completes, the HTTP response to send.</param> /// <param name="bot">The bot that will handle the incoming activity.</param> /// <param name="cancellationToken">A cancellation token for the task.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public async Task ProcessAsync(HttpRequest request, HttpResponse response, IBot bot, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } if (bot == null) { throw new ArgumentNullException(nameof(bot)); } string body; using (var sr = new StreamReader(request.Body)) { body = await sr.ReadToEndAsync().ConfigureAwait(false); } var slackBody = SlackHelper.DeserializeBody(body); if (slackBody.Type == "url_verification") { var text = slackBody.Challenge; await SlackHelper.WriteAsync(response, HttpStatusCode.OK, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); return; } if (!_slackClient.VerifySignature(request, body)) { const string text = "Rejected due to mismatched header signature"; await SlackHelper.WriteAsync(response, HttpStatusCode.Unauthorized, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); throw new Exception(text); } if (!string.IsNullOrWhiteSpace(_slackClient.Options.SlackVerificationToken) && slackBody.Token != _slackClient.Options.SlackVerificationToken) { var text = $"Rejected due to mismatched verificationToken:{slackBody}"; await SlackHelper.WriteAsync(response, HttpStatusCode.Forbidden, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); throw new Exception(text); } Activity activity; if (slackBody.Payload != null) { // handle interactive_message callbacks and block_actions activity = SlackHelper.PayloadToActivity(slackBody.Payload); } else if (slackBody.Type == "event_callback") { // this is an event api post activity = await SlackHelper.EventToActivityAsync(slackBody.Event, _slackClient, cancellationToken).ConfigureAwait(false); } else if (slackBody.Command != null) { activity = await SlackHelper.CommandToActivityAsync(slackBody, _slackClient, cancellationToken).ConfigureAwait(false); } else { throw new Exception($"Unknown Slack event type {slackBody.Type}"); } using (var context = new TurnContext(this, activity)) { context.TurnState.Add("httpStatus", ((int)HttpStatusCode.OK).ToString(System.Globalization.CultureInfo.InvariantCulture)); await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false); var code = Convert.ToInt32(context.TurnState.Get <string>("httpStatus"), System.Globalization.CultureInfo.InvariantCulture); var statusCode = (HttpStatusCode)code; var text = context.TurnState.Get <object>("httpBody") != null?context.TurnState.Get <object>("httpBody").ToString() : string.Empty; await SlackHelper.WriteAsync(response, statusCode, text, Encoding.UTF8, cancellationToken).ConfigureAwait(false); } }