HTTP WebHook validation

Signed-off-by: clemensv <clemensv@microsoft.com>
This commit is contained in:
clemensv 2018-11-28 20:21:56 +01:00
parent 2e88ade403
commit b9f10957bf
2 changed files with 160 additions and 1 deletions

View File

@ -77,6 +77,127 @@ namespace CloudNative.CloudEvents
await stream.CopyToAsync(httpWebRequest.GetRequestStream());
}
/// <summary>
/// Handle the request as WebHook validation request
/// </summary>
/// <param name="httpRequestMessage">Request</param>
/// <param name="validateOrigin">Callback that returns whether the given origin may push events. If 'null', all origins are acceptable.</param>
/// <param name="validateRate">Callback that returns the acceptable request rate. If 'null', the rate is not limited.</param>
/// <returns>Response</returns>
public static async Task<HttpResponseMessage> HandleAsWebHookValidationRequest(
this HttpRequestMessage httpRequestMessage, Func<string, bool> validateOrigin,
Func<string, string> validateRate)
{
if (IsWebHookValidationRequest(httpRequestMessage))
{
var origin = httpRequestMessage.Headers.GetValues("WebHook-Request-Origin").FirstOrDefault();
var rate = httpRequestMessage.Headers.GetValues("WebHook-Request-Rate").FirstOrDefault();
if (origin != null && (validateOrigin == null || validateOrigin(origin)))
{
if (rate != null)
{
if (validateRate != null)
{
rate = validateRate(rate);
}
else
{
rate = "*";
}
}
if (httpRequestMessage.Headers.Contains("WebHook-Request-Callback"))
{
var uri = httpRequestMessage.Headers.GetValues("WebHook-Request-Callback").FirstOrDefault();
try
{
HttpClient client = new HttpClient();
var response = await client.GetAsync(new Uri(uri));
return new HttpResponseMessage(response.StatusCode);
}
catch (Exception e)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
else
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Headers.Add("Allow", "POST");
response.Headers.Add("WebHook-Allowed-Origin", origin);
response.Headers.Add("WebHook-Allowed-Rate", rate);
return response;
}
}
}
return new HttpResponseMessage(HttpStatusCode.MethodNotAllowed);
}
/// <summary>
/// Handle the request as WebHook validation request
/// </summary>
/// <param name="context">Request context</param>
/// <param name="validateOrigin">Callback that returns whether the given origin may push events. If 'null', all origins are acceptable.</param>
/// <param name="validateRate">Callback that returns the acceptable request rate. If 'null', the rate is not limited.</param>
/// <returns>Task</returns>
public static async Task HandleAsWebHookValidationRequest(this HttpListenerContext context,
Func<string, bool> validateOrigin, Func<string, string> validateRate)
{
if (IsWebHookValidationRequest(context.Request))
{
var origin = context.Request.Headers.Get("WebHook-Request-Origin");
var rate = context.Request.Headers.Get("WebHook-Request-Rate");
if (origin != null && (validateOrigin == null || validateOrigin(origin)))
{
if (rate != null)
{
if (validateRate != null)
{
rate = validateRate(rate);
}
else
{
rate = "*";
}
}
if (context.Request.Headers["WebHook-Request-Callback"] != null)
{
var uri = context.Request.Headers.Get("WebHook-Request-Callback");
try
{
HttpClient client = new HttpClient();
var response = await client.GetAsync(new Uri(uri));
context.Response.StatusCode = (int)response.StatusCode;
context.Response.Close();
return;
}
catch (Exception e)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.Close();
return;
}
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
context.Response.Headers.Add("Allow", "POST");
context.Response.Headers.Add("WebHook-Allowed-Origin", origin);
context.Response.Headers.Add("WebHook-Allowed-Rate", rate);
context.Response.Close();
return;
}
}
}
context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
context.Response.Close();
}
/// <summary>
/// Indicates whether this HttpResponseMessage holds a CloudEvent
/// </summary>
@ -101,6 +222,24 @@ namespace CloudNative.CloudEvents
httpListenerRequest.Headers[SpecVersionHttpHeader2] != null;
}
/// <summary>
/// Indicates whether this HttpListenerRequest is a web hook validation request
/// </summary>
public static bool IsWebHookValidationRequest(this HttpRequestMessage httpRequestMessage)
{
return (httpRequestMessage.Method.Method.Equals("options", StringComparison.InvariantCultureIgnoreCase) &&
httpRequestMessage.Headers.Contains("WebHook-Request-Origin"));
}
/// <summary>
/// Indicates whether this HttpListenerRequest is a web hook validation request
/// </summary>
public static bool IsWebHookValidationRequest(this HttpListenerRequest httpRequestMessage)
{
return (httpRequestMessage.HttpMethod.Equals("options", StringComparison.InvariantCultureIgnoreCase) &&
httpRequestMessage.Headers["WebHook-Request-Origin"] != null);
}
/// <summary>
/// Converts this response message into a CloudEvent object, with the given extensions.
/// </summary>
@ -382,7 +521,7 @@ namespace CloudNative.CloudEvents
else
{
attributes[name] = headerValue;
}
}
}
}

View File

@ -7,6 +7,7 @@ namespace CloudNative.CloudEvents.UnitTests
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
@ -49,6 +50,13 @@ namespace CloudNative.CloudEvents.UnitTests
async Task HandleContext(HttpListenerContext requestContext)
{
var ctxHeaderValue = requestContext.Request.Headers[testContextHeader];
if (requestContext.Request.IsWebHookValidationRequest())
{
await requestContext.HandleAsWebHookValidationRequest(null, null);
return;
}
if (pendingRequests.TryRemove(ctxHeaderValue, out var pending))
{
await pending(requestContext);
@ -64,6 +72,18 @@ namespace CloudNative.CloudEvents.UnitTests
#pragma warning restore 4014
}
[Fact]
async Task HttpWebHookValidation()
{
var httpClient = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Options, new Uri(listenerAddress + "ep"));
req.Headers.Add("WebHook-Request-Origin", "example.com");
req.Headers.Add("WebHook-Request-Rate", "120");
var result = await httpClient.SendAsync( req );
Assert.Equal("example.com", result.Headers.GetValues("WebHook-Allowed-Origin").First());
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
[Fact]
async Task HttpBinaryClientReceiveTest()
{