protected virtual void SignWebHookRequest(WebHookWorkItem workItem, HttpRequestMessage request, JObject body) { if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); } if (workItem.WebHook == null) { string msg = "WebHook"; throw new ArgumentException(msg, "workItem"); } if (request == null) { throw new ArgumentNullException(nameof(request)); } if (body == null) { throw new ArgumentNullException(nameof(body)); } byte[] secret = Encoding.UTF8.GetBytes(workItem.WebHook.Secret); using (var hasher = new HMACSHA256(secret)) { string serializedBody = body.ToString(); request.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json"); byte[] data = Encoding.UTF8.GetBytes(serializedBody); byte[] sha256 = hasher.ComputeHash(data); string headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, EncodingUtilities.ToHex(sha256)); request.Headers.Add(SignatureHeaderName, headerValue); } }
protected virtual HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem) { if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); } WebHook hook = workItem.WebHook; // Create WebHook request HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, hook.WebHookUri); // Fill in request body based on WebHook and work item data JObject body = CreateWebHookRequestBody(workItem); SignWebHookRequest(workItem, request, body); // Add extra request or entity headers foreach (var kvp in hook.Headers) { if (!request.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value)) { if (!request.Content.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value)) { //string msg = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_InvalidHeader, kvp.Key, hook.Id); //_logger.Error(msg); } } } return(request); }
protected JObject CreateWebHookRequestBody(WebHookWorkItem workItem) { if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); } Dictionary <string, object> body = new Dictionary <string, object>(); // Set properties from work item body[BodyIdKey] = workItem.Id; body[BodyAttemptKey] = workItem.Offset + 1; // Set properties from WebHook IDictionary <string, object> properties = workItem.WebHook.Properties; if (properties != null) { body[BodyPropertiesKey] = new Dictionary <string, object>(properties); } // Set notifications body[BodyNotificationsKey] = workItem.Notifications; return(JObject.FromObject(body)); }
public void CreateWebHookRequest_CreatesExpectedRequest() { // Arrange WebHookWorkItem workItem = CreateWorkItem(); workItem.WebHook.Headers.Add("Content-Language", "da"); _sender = new WebHookSenderMock(_loggerMock.Object); // Act HttpRequestMessage actual = _sender.CreateWebHookRequest(workItem); // Assert Assert.Equal(HttpMethod.Post, actual.Method); Assert.Equal(workItem.WebHook.WebHookUri, actual.RequestUri); IEnumerable <string> headers; actual.Headers.TryGetValues("h1", out headers); Assert.Equal("hv1", headers.Single()); actual.Headers.TryGetValues("ms-signature", out headers); Assert.Equal(WebHookSignature, headers.Single()); Assert.Equal("da", actual.Content.Headers.ContentLanguage.Single()); Assert.Equal("application/json; charset=utf-8", actual.Content.Headers.ContentType.ToString()); }
internal static IEnumerable <WebHookWorkItem> GetWorkItems(ICollection <WebHook> webHooks, ICollection <NotificationDictionary> notifications) { var workItems = new List <WebHookWorkItem>(); foreach (var webHook in webHooks) { ICollection <NotificationDictionary> webHookNotifications; // Pick the notifications that apply for this particular WebHook. If we only got one notification // then we know that it applies to all WebHooks. Otherwise each notification may apply only to a subset. if (notifications.Count == 1) { webHookNotifications = notifications; } else { webHookNotifications = notifications.Where(n => webHook.MatchesAction(n.Action)).ToArray(); if (webHookNotifications.Count == 0) { continue; } } var workItem = new WebHookWorkItem(webHook, webHookNotifications); workItems.Add(workItem); } return(workItems); }
public WebHookWorkItemTests() { _notification = new Notification("action", payload: null); _notifications = new List <Notification> { _notification }; _webHook = new WebHook(); _workItem = new WebHookWorkItem(_webHook, _notifications.First()); }
private static WebHookWorkItem CreateWorkItem() { WebHook webHook = WebHookManagerTests.CreateWebHook(); Notification notification = new Notification("a1", new { d1 = "dv1" }); // Notification notification2 = new Notification("a1", new Dictionary<string, object> { { "d2", new Uri("http://localhost") } }); WebHookWorkItem workItem = new WebHookWorkItem(webHook, notification) { Id = "1234567890", }; return(workItem); }
public void CreateWebHookRequestBody_CreatesExpectedBody() { // Arrange WebHookWorkItem workItem = CreateWorkItem(); _sender = new WebHookSenderMock(_loggerMock.Object); // Act JObject actual = _sender.CreateWebHookRequestBody(workItem); // Assert Assert.Equal(SerializedWebHook, actual.ToString()); }
public async Task <IHttpActionResult> Post(WebHook webHook) { if (webHook == null) { return(BadRequest()); } webHook.WebHookUri = new Uri("https://webhook.site/e6e278c0-1958-4185-9c4d-f7cee654cd7f"); webHook.Secret = Guid.NewGuid().ToString(); string userId = await GetUserId(); await VerifyFilters(webHook); // await VerifyWebHook(webHook); try { // Ensure we have a normalized ID for the WebHook webHook.Id = null; // Add WebHook for this user. StoreResult result = await _store.InsertWebHookAsync(userId, webHook); if (result == StoreResult.Success) { NotificationDictionary notification = new NotificationDictionary("created", null); WebHookWorkItem workItem = new WebHookWorkItem(webHook, new [] { notification }); // WebHookWorkItem item = new WebHookWorkItem(webHook, ); IEnumerable <WebHookWorkItem> workItems = new[] { workItem }; await this.NotifyAsync("event1", new { P1 = "p1" }); await _sender.SendWebHookWorkItemsAsync(workItems); // var a = _manager.NotifyAsync(userId, new[] {notification}, null); return(CreatedAtRoute("WebhookHandler", new { id = webHook.Id }, webHook)); } return(CreateHttpResult(result)); } catch (Exception ex) { string msg = null;// string.Format(CultureInfo.CurrentCulture, CustomApiResources.RegistrationController_RegistrationFailure, ex.Message); HttpResponseMessage error = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, msg, ex); return(ResponseMessage(error)); } }
public async Task SendWebHook_StopsOnLastLastFailureOrFirstSuccessAndFirstGone(TimeSpan[] delays, Func <HttpRequestMessage, int, Task <HttpResponseMessage> > handler, int expectedOffset, SendResult expectedResult) { // Arrange var actualResult = SendResult.None; var done = new ManualResetEvent(initialState: false); WebHookWorkItem final = null; var actualRetries = 0; _sender = new TestDataflowWebHookSender(_loggerMock.Object, delays, _options, _httpClient, _mvcJsonOptions, onWebHookRetry: item => { actualRetries++; }, onWebHookSuccess: item => { final = item; actualResult = SendResult.Success; done.Set(); }, onWebHookGone: item => { final = item; actualResult = SendResult.Gone; done.Set(); }, onWebHookFailure: item => { final = item; actualResult = SendResult.Failure; done.Set(); }); _handlerMock.Handler = handler; var notification = new Notification("a1", new { a1 = "a1" }); var webHook = WebHookManagerTests.CreateWebHook(); var workItem = new WebHookWorkItem(webHook, notification) { Id = "1234567890", }; // Act await _sender.SendWebHookWorkItemsAsync(new[] { workItem }); done.WaitOne(); // Assert var expectedRetries = expectedResult == SendResult.Failure ? Math.Max(0, expectedOffset - 1) : expectedOffset; Assert.Equal(expectedRetries, actualRetries); Assert.Equal(expectedResult, actualResult); Assert.Equal(expectedOffset, final.Offset); }
private async Task LaunchWebHook(WebHookWorkItem workItem) { try { HttpClient _httpClient = new HttpClient(); // Setting up and send WebHook request HttpRequestMessage request = CreateWebHookRequest(workItem); HttpResponseMessage response = await _httpClient.SendAsync(request); } catch (Exception ex) { Logger.Error(ex.ToString()); } }
public void LaunchHook(string uri, string secret, string action, object data) { Task.Run(() => { var notifications = new[] { new NotificationDictionary(action, data) }; var webHook = new Microsoft.AspNet.WebHooks.WebHook { Description = action, WebHookUri = new Uri(uri), Secret = secret }; var workItem = new WebHookWorkItem(webHook, notifications); SendWebHookWorkItemsAsync(new[] { workItem }); }); }
public void SignWebHookRequest_HandlesNullWebHook() { WebHookWorkItem workItem = CreateWorkItem(); HttpRequestMessage request = new HttpRequestMessage(); _sender = new WebHookSenderMock(_loggerMock.Object); JObject body = _sender.CreateWebHookRequestBody(workItem); workItem.WebHook = null; // Act ArgumentException ex = Assert.Throws <ArgumentException>(() => _sender.SignWebHookRequest(workItem, request, body)); // Assert Assert.StartsWith("Invalid 'WebHookSenderMock' instance: 'WebHook' cannot be null.", ex.Message); }
/// <inheritdoc /> protected override JObject CreateWebHookRequestBody(WebHookWorkItem workItem) { JObject data = base.CreateWebHookRequestBody(workItem); Dictionary <string, object> body = data.ToObject <Dictionary <string, object> >(); // The web hook id is added to the web hook body. // This is required in order to properly validate the web hook. // When a web hook is created, it is created with a Secred field. // The web hook id and the secret can be stored in the client's database, so that when a web hook is received // it can be validated with the secret in the database. // This ensures that the web hook is send from the proper location and that it's content were not tampered with. body[WebHookIdKey] = workItem.WebHook.Id; return(JObject.FromObject(body)); }
public static IEnumerable<WebHookWorkItem> CreateWorkItems(int count) { WebHookWorkItem[] workItems = new WebHookWorkItem[count]; for (int cnt = 0; cnt < count; cnt++) { WebHook webHook = new WebHook { WebHookUri = new Uri("http://localhost/path/" + count), Secret = "0123456789012345678901234567890123456789" + count }; NotificationDictionary notification = new NotificationDictionary("a" + cnt, cnt); WebHookWorkItem workItem = new WebHookWorkItem(webHook, new[] { notification }); workItem.Properties[AzureWebHookDequeueManager.QueueMessageKey] = new CloudQueueMessage("content"); workItems[cnt] = workItem; } return workItems; }
public static IEnumerable <WebHookWorkItem> CreateWorkItems(int count) { WebHookWorkItem[] workItems = new WebHookWorkItem[count]; for (int cnt = 0; cnt < count; cnt++) { WebHook webHook = new WebHook { WebHookUri = new Uri("http://localhost/path/" + count), Secret = "0123456789012345678901234567890123456789" + count }; NotificationDictionary notification = new NotificationDictionary("a" + cnt, cnt); WebHookWorkItem workItem = new WebHookWorkItem(webHook, new[] { notification }); workItem.Properties[AzureWebHookDequeueManager.QueueMessageKey] = new CloudQueueMessage("content"); workItems[cnt] = workItem; } return(workItems); }
/// <summary> /// Fetches the authorization type required for this webhook mentioned in the prperties /// </summary> /// <param name="webHookWorkItem">WebHookWorkItem</param> /// <returns>AuthorizationType</returns> private AuthorizationType GetAuthType(WebHookWorkItem webHookWorkItem) { Logger?.Info("GetAuthType::Start"); string authType = GetWebHookPropertiesValue(webHookWorkItem.WebHook, CustomResources.Authorization_Type); Logger?.Info($"GetAuthType::Authtype: {authType} for WebHook Id: {webHookWorkItem.WebHook.Id}"); switch (authType) { case "oauth": return(AuthorizationType.oAuth); case "basic": return(AuthorizationType.Basic); default: return(AuthorizationType.NoAuth); } }
/// <summary> /// Creates a <see cref="JObject"/> used as the <see cref="HttpRequestMessage"/> entity body for a <see cref="WebHook"/>. /// </summary> /// <param name="workItem">The <see cref="WebHookWorkItem"/> representing the data to be sent.</param> /// <returns>An initialized <see cref="JObject"/>.</returns> protected virtual void CreateWebHookRequestBody(WebHookWorkItem workItem, StreamWriter writer) { if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); } // Set notifications var webhookBody = new WebhookBody { Id = workItem.Id, Attempt = workItem.Offset + 1, }; var properties = workItem.WebHook.Properties; if (properties != null) { webhookBody.Properties = new Dictionary <string, object>(properties); } webhookBody.Notifications = workItem.Notifications.ToArray(); _serializer.Serialize(writer, webhookBody); }
/// <summary> /// Launch a <see cref="WebHook"/>. /// </summary> /// <remarks>We don't let exceptions propagate out from this method as it is used by the launchers /// and if they see an exception they shut down.</remarks> private async Task LaunchWebHook(WebHookWorkItem workItem) { try { // Setting up and send WebHook request HttpRequestMessage request = CreateWebHookRequest(workItem); HttpResponseMessage response = await _httpClient.SendAsync(request); string msg = string.Format(CultureInfo.CurrentCulture, BaladorResource.Manager_Result, workItem.WebHook.Id, response.StatusCode, workItem.Offset); Logger.Info(msg); if (response.IsSuccessStatusCode) { if (workItem.WebHook.Filters != null && workItem.WebHook.Filters.Any() && workItem.WebHook.Filters.Where(p => p == BaladorConst.PreUpdate).FirstOrDefault() != null) { var messageResult = await response.Content.ReadAsAsync <SendRequest>(); workItem.Properties.Add(BaladorConst.Messages, messageResult); await OnWebHookSuccess(workItem); return; } } else if (response.StatusCode == HttpStatusCode.Gone) { // If we get a 410 Gone then we are also done. await OnWebHookGone(workItem); await OnWebHookFailure(workItem); return; } } catch (Exception ex) { string msg = string.Format(CultureInfo.CurrentCulture, BaladorResource.Manager_WebHookFailure, workItem.Offset, workItem.WebHook.Id, ex.Message); Logger.Error(msg, ex); } }
public async Task SignWebHookRequest_SignsBodyCorrectly() { // Arrange WebHookWorkItem workItem = CreateWorkItem(); HttpRequestMessage request = new HttpRequestMessage(); _sender = new WebHookSenderMock(_loggerMock.Object); JObject body = _sender.CreateWebHookRequestBody(workItem); // Act _sender.SignWebHookRequest(workItem, request, body); // Assert IEnumerable <string> signature; request.Headers.TryGetValues("ms-signature", out signature); Assert.Equal(WebHookSignature, signature.Single()); string requestBody = await request.Content.ReadAsStringAsync(); Assert.Equal(SerializedWebHook, requestBody); Assert.Equal("application/json; charset=utf-8", request.Content.Headers.ContentType.ToString()); }
protected virtual async Task <HttpRequestMessage> CreateWebHookRequestAsync(WebHookWorkItem workItem) { if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); } var hook = workItem.WebHook; // Create WebHook request var request = new HttpRequestMessage(HttpMethod.Post, hook.WebHookUri); var ms = new MemoryStream(); var writer = new StreamWriter(ms); CreateWebHookRequestBody(workItem, writer); await writer.FlushAsync(); ms.Seek(0, SeekOrigin.Begin); SignWebHookRequest(workItem, request, ms); // Add extra request or entity headers foreach (var kvp in hook.Headers) { if (!request.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value)) { if (!request.Content.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value)) { var message = string.Format(CultureInfo.CurrentCulture, CustomResources.Manager_InvalidHeader, kvp.Key, hook.Id); _logger.LogError(message); } } } return(request); }
/// <summary> /// Adds a SHA 256 signature to the <paramref name="body"/> and adds it to the <paramref name="request"/> as an /// HTTP header to the <see cref="HttpRequestMessage"/> along with the entity body. /// </summary> /// <param name="workItem">The current <see cref="WebHookWorkItem"/>.</param> /// <param name="request">The request to add the signature to.</param> /// <param name="body">The body to sign and add to the request.</param> protected virtual void SignWebHookRequest(WebHookWorkItem workItem, HttpRequestMessage request, Stream body) { if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); } if (workItem.WebHook == null) { var message = string.Format(CultureInfo.CurrentCulture, CustomResources.Sender_BadWorkItem, GetType().Name, "WebHook"); throw new ArgumentException(message, "workItem"); } if (request == null) { throw new ArgumentNullException(nameof(request)); } if (body == null) { throw new ArgumentNullException(nameof(body)); } var secret = Encoding.UTF8.GetBytes(workItem.WebHook.Secret); using var hasher = new HMACSHA256(secret); var sha256 = hasher.ComputeHash(body); var headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, EncodingUtilities.ToHex(sha256)); request.Headers.Add(SignatureHeaderName, headerValue); body.Seek(0, SeekOrigin.Begin); var content = new StreamContent(body); content.Headers.ContentType = ApplicationJson; request.Content = content; }
public new HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem) { return(base.CreateWebHookRequest(workItem)); }
public new JObject CreateWebHookRequestBody(WebHookWorkItem workItem) { return(base.CreateWebHookRequestBody(workItem)); }
protected override Task OnWebHookSuccess(WebHookWorkItem workItem) { this.Logger.Log(TraceLevel.Info, $"[SCION] Webhook '{workItem.WebHook.Id}' sent.", null); return(Task.FromResult(true)); }
public new void SignWebHookRequest(WebHookWorkItem workItem, HttpRequestMessage request, JObject body) { base.SignWebHookRequest(workItem, request, body); }
/// <summary> /// By overriding this method you can control just the WebHook request body. The rest of the /// WebHook request follows the default model including the HTTP header containing a signature of the /// body. /// </summary> protected override JObject CreateWebHookRequestBody(WebHookWorkItem workItem) { return(base.CreateWebHookRequestBody(workItem)); }
/// <summary> /// By overriding this method you can shape the WebHook request URI exactly as you want, both /// in terms of HTTP headers and HTTP body. /// </summary> protected override HttpRequestMessage CreateWebHookRequest(WebHookWorkItem workItem) { return(base.CreateWebHookRequest(workItem)); }
/// <summary> /// By overriding this method you can control just the WebHook header containing a signature of the /// body. /// </summary> protected override void SignWebHookRequest(WebHookWorkItem workItem, HttpRequestMessage request, JObject body) { base.SignWebHookRequest(workItem, request, body); }
protected override Task OnWebHookGone(WebHookWorkItem workItem) { this.Logger.Log(TraceLevel.Error, $"[SCION] Webhook '{workItem.WebHook.Id}' sent failed.", null); return(DeleteWebHook(workItem.WebHook.Id)); }
protected override Task OnWebHookRetry(WebHookWorkItem workItem) { this.Logger.Log(TraceLevel.Warn, $"[SCION] Webhook '{workItem.WebHook.Id}' in retry ${workItem.Offset}.", null); return(Task.FromResult(true)); }