webhooks API & client
This commit is contained in:
parent
38243a9d2f
commit
9b0eaddb15
@ -397,7 +397,6 @@ services:
|
|||||||
|
|
||||||
webhooks.client:
|
webhooks.client:
|
||||||
environment:
|
environment:
|
||||||
- ASPNETCORE_ENVIRONMENT=Development
|
|
||||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||||
- Token=6168DB8D-DC58-4094-AF24-483278923590 # Webhooks are registered with this token (any value is valid) but the client won't check it
|
- Token=6168DB8D-DC58-4094-AF24-483278923590 # Webhooks are registered with this token (any value is valid) but the client won't check it
|
||||||
- IdentityUrl=http://10.0.75.1:5105
|
- IdentityUrl=http://10.0.75.1:5105
|
||||||
|
@ -38,7 +38,7 @@ namespace Webhooks.API.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet("{id:int}", Name = "Get")]
|
[HttpGet("{id:int}")]
|
||||||
[ProducesResponseType(typeof(WebhookSubscription), (int)HttpStatusCode.OK)]
|
[ProducesResponseType(typeof(WebhookSubscription), (int)HttpStatusCode.OK)]
|
||||||
[ProducesResponseType((int)HttpStatusCode.NotFound)]
|
[ProducesResponseType((int)HttpStatusCode.NotFound)]
|
||||||
public async Task<IActionResult> GetByUserAndId(int id)
|
public async Task<IActionResult> GetByUserAndId(int id)
|
||||||
@ -82,7 +82,7 @@ namespace Webhooks.API.Controllers
|
|||||||
|
|
||||||
_dbContext.Add(subscription);
|
_dbContext.Add(subscription);
|
||||||
await _dbContext.SaveChangesAsync();
|
await _dbContext.SaveChangesAsync();
|
||||||
return CreatedAtAction("Get", new { id = subscription.Id });
|
return CreatedAtAction("GetByUserAndId", new { id = subscription.Id }, subscription);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@ -9,19 +10,30 @@ namespace Webhooks.API.Services
|
|||||||
class GrantUrlTesterService : IGrantUrlTesterService
|
class GrantUrlTesterService : IGrantUrlTesterService
|
||||||
{
|
{
|
||||||
private readonly IHttpClientFactory _clientFactory;
|
private readonly IHttpClientFactory _clientFactory;
|
||||||
public GrantUrlTesterService(IHttpClientFactory factory)
|
private readonly ILogger _logger;
|
||||||
|
public GrantUrlTesterService(IHttpClientFactory factory, ILogger<IGrantUrlTesterService> logger)
|
||||||
{
|
{
|
||||||
_clientFactory = factory;
|
_clientFactory = factory;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> TestGrantUrl(string url, string token)
|
public async Task<bool> TestGrantUrl(string url, string token)
|
||||||
{
|
{
|
||||||
var client = _clientFactory.CreateClient("GrantClient");
|
var client = _clientFactory.CreateClient("GrantClient");
|
||||||
|
|
||||||
var msg = new HttpRequestMessage(HttpMethod.Options, url);
|
var msg = new HttpRequestMessage(HttpMethod.Options, url);
|
||||||
msg.Headers.Add("X-eshop-whtoken", token);
|
msg.Headers.Add("X-eshop-whtoken", token);
|
||||||
|
_logger.LogTrace($"Sending the OPTIONS message to {url} with token {token ?? string.Empty}");
|
||||||
|
try
|
||||||
|
{
|
||||||
var response = await client.SendAsync(msg);
|
var response = await client.SendAsync(msg);
|
||||||
|
_logger.LogInformation($"Response code is {response.StatusCode} for url {url}");
|
||||||
return response.IsSuccessStatusCode;
|
return response.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Exception {ex.GetType().Name} when sending OPTIONS request. Url can't be granted.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
src/Web/WebhookClient/.dockerignore
Normal file
1
src/Web/WebhookClient/.dockerignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
!wwwroot
|
@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WebhookClient.Models;
|
||||||
|
|
||||||
|
namespace WebhookClient.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("webhook-received")]
|
||||||
|
public class WebhooksReceivedController : Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly Settings _settings;
|
||||||
|
|
||||||
|
public WebhooksReceivedController(IOptions<Settings> settings)
|
||||||
|
{
|
||||||
|
_settings = settings.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult NewWebhook(WebhookData hook)
|
||||||
|
{
|
||||||
|
var header = Request.Headers[HeaderNames.WebHookCheckHeader];
|
||||||
|
var token = header.FirstOrDefault();
|
||||||
|
if (!_settings.ValidateToken || _settings.Token == token)
|
||||||
|
{
|
||||||
|
var received = HttpContext.Session.Get<IEnumerable<WebHookReceived>>(SessionKeys.HooksKey)?.ToList() ?? new List<WebHookReceived>();
|
||||||
|
received.Add(new WebHookReceived()
|
||||||
|
{
|
||||||
|
Data = hook.Payload,
|
||||||
|
When = hook.When,
|
||||||
|
Token = token
|
||||||
|
});
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,5 +9,7 @@ namespace WebhookClient.Models
|
|||||||
{
|
{
|
||||||
public DateTime When { get; set; }
|
public DateTime When { get; set; }
|
||||||
public string Data { get; set; }
|
public string Data { get; set; }
|
||||||
|
|
||||||
|
public string Token { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
src/Web/WebhookClient/Models/WebhookData.cs
Normal file
16
src/Web/WebhookClient/Models/WebhookData.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebhookClient.Models
|
||||||
|
{
|
||||||
|
public class WebhookData
|
||||||
|
{
|
||||||
|
public DateTime When { get; }
|
||||||
|
|
||||||
|
public string Payload { get; }
|
||||||
|
|
||||||
|
public string Type { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -7,21 +7,25 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h1 class="display-4">Welcome</h1>
|
<h1 class="display-4">Welcome</h1>
|
||||||
<p>eShopOnContainers - Webhook client</p>
|
<p>eShopOnContainers - Webhook client</p>
|
||||||
<a class="btn-primary btn" href="@Url.Action("SignIn","Account")">Login</a>
|
@if (!User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
<a class="btn-primary btn" href="@Url.Action("SignIn", "Account")">Login</a>
|
||||||
<p>Why I need to login? You only need to login <bold>to setup a new webhook</bold>.</p>
|
<p>Why I need to login? You only need to login <bold>to setup a new webhook</bold>.</p>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (User.Identity.IsAuthenticated)
|
@if (User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
|
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<h3>Current webhooks invoked</h3>
|
<h3>Current webhooks received</h3>
|
||||||
|
<p>(Data since last time web started up)<p>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
@foreach (var webhook in Model.WebHooksReceived)
|
@foreach (var webhook in Model.WebHooksReceived)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@webhook.When</td>
|
<td>@webhook.When</td>
|
||||||
<td><pre>@webhook.Data</pre></td>
|
<td><pre>@webhook.Data</pre></td>
|
||||||
|
<td>@(webhook.Token ?? "--None--")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</table>
|
</table>
|
||||||
|
@ -17,7 +17,7 @@ namespace WebhookClient.Pages
|
|||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
{
|
{
|
||||||
WebHooksReceived = HttpContext.Session.Get<IEnumerable<WebHookReceived>>("webhooks.received") ?? Enumerable.Empty<WebHookReceived>();
|
WebHooksReceived = HttpContext.Session.Get<IEnumerable<WebHookReceived>>(SessionKeys.HooksKey) ?? Enumerable.Empty<WebHookReceived>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,3 +12,8 @@
|
|||||||
<p>Token: <input type="text" asp-for="Token" /></p>
|
<p>Token: <input type="text" asp-for="Token" /></p>
|
||||||
<input type="submit" value="send" />
|
<input type="submit" value="send" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@if (Model.ResponseCode != (int)System.Net.HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
<p>Error @Model.ResponseCode (@Model.ResponseMessage) when calling the Webhooks API (@Model.RequestUrl) with GrantUrl: @Model.GrantUrl):(</p>
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Formatting;
|
using System.Net.Http.Formatting;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -20,6 +21,11 @@ namespace WebhookClient.Pages
|
|||||||
|
|
||||||
[BindProperty] public string Token { get; set; }
|
[BindProperty] public string Token { get; set; }
|
||||||
|
|
||||||
|
public int ResponseCode { get; set; }
|
||||||
|
public string RequestUrl { get; set; }
|
||||||
|
public string GrantUrl { get; set; }
|
||||||
|
public string ResponseMessage { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public RegisterWebhookModel(IOptions<Settings> settings, IHttpClientFactory httpClientFactory)
|
public RegisterWebhookModel(IOptions<Settings> settings, IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
@ -29,15 +35,21 @@ namespace WebhookClient.Pages
|
|||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
{
|
{
|
||||||
|
ResponseCode = (int)HttpStatusCode.OK;
|
||||||
Token = _settings.Token;
|
Token = _settings.Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnPost()
|
public async Task<IActionResult> OnPost()
|
||||||
{
|
{
|
||||||
|
ResponseCode = (int)HttpStatusCode.OK;
|
||||||
var protocol = Request.IsHttps ? "https" : "http";
|
var protocol = Request.IsHttps ? "https" : "http";
|
||||||
var selfurl = !string.IsNullOrEmpty(_settings.SelfUrl) ? _settings.SelfUrl : $"{protocol}://{Request.Host}/{Request.PathBase}";
|
var selfurl = !string.IsNullOrEmpty(_settings.SelfUrl) ? _settings.SelfUrl : $"{protocol}://{Request.Host}/{Request.PathBase}";
|
||||||
|
if (!selfurl.EndsWith("/"))
|
||||||
|
{
|
||||||
|
selfurl = selfurl + "/";
|
||||||
|
}
|
||||||
var granturl = $"{selfurl}check";
|
var granturl = $"{selfurl}check";
|
||||||
var url = $"{selfurl}webhook";
|
var url = $"{selfurl}webhook-received";
|
||||||
var client = _httpClientFactory.CreateClient("GrantClient");
|
var client = _httpClientFactory.CreateClient("GrantClient");
|
||||||
|
|
||||||
var payload = new WebhookSubscriptionRequest()
|
var payload = new WebhookSubscriptionRequest()
|
||||||
@ -49,7 +61,19 @@ namespace WebhookClient.Pages
|
|||||||
};
|
};
|
||||||
var response = await client.PostAsync<WebhookSubscriptionRequest>(_settings.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter());
|
var response = await client.PostAsync<WebhookSubscriptionRequest>(_settings.WebhooksUrl + "/api/v1/webhooks", payload, new JsonMediaTypeFormatter());
|
||||||
|
|
||||||
RedirectToPage("Index");
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return RedirectToPage("WebhooksList");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ResponseCode = (int)response.StatusCode;
|
||||||
|
ResponseMessage = response.ReasonPhrase;
|
||||||
|
GrantUrl = granturl;
|
||||||
|
RequestUrl = $"{response.RequestMessage.Method} {response.RequestMessage.RequestUri}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Page();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -32,7 +32,10 @@
|
|||||||
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
|
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
|
<a class="nav-link text-dark" asp-area="" asp-page="/RegisterWebhook">Register webhook</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-dark" asp-area="" asp-page="/WebhooksList">Webhooks registered (in API)</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +50,7 @@
|
|||||||
|
|
||||||
<footer class="border-top footer text-muted">
|
<footer class="border-top footer text-muted">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
© 2019 - WebhookClient - <a asp-area="" asp-page="/Privacy">Privacy</a>
|
© 2019 - WebhookClient - <a asp-area="" asp-page="/RegisterWebhook">Register Webhook</a> | <a asp-area="" asp-page="/WebhooksList">Webhooks registered in API</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
using System;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using WebhookClient.Models;
|
using WebhookClient.Models;
|
||||||
|
|
||||||
@ -7,23 +10,21 @@ namespace WebhookClient.Services
|
|||||||
{
|
{
|
||||||
public class WebhooksClient : IWebhooksClient
|
public class WebhooksClient : IWebhooksClient
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly Settings _settings;
|
||||||
|
public WebhooksClient(IHttpClientFactory httpClientFactory, IOptions<Settings> settings)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_settings = settings.Value;
|
||||||
|
}
|
||||||
public async Task<IEnumerable<WebhookResponse>> LoadWebhooks()
|
public async Task<IEnumerable<WebhookResponse>> LoadWebhooks()
|
||||||
{
|
{
|
||||||
return new[]{
|
var client = _httpClientFactory.CreateClient("GrantClient");
|
||||||
new WebhookResponse()
|
var response = await client.GetAsync(_settings.WebhooksUrl + "/api/v1/webhooks");
|
||||||
{
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
Date = DateTime.Now,
|
var subscriptions = JsonConvert.DeserializeObject<IEnumerable<WebhookResponse>>(json);
|
||||||
DestUrl = "http://aaaaa.me",
|
return subscriptions;
|
||||||
Token = "3282832as2"
|
|
||||||
},
|
|
||||||
new WebhookResponse()
|
|
||||||
{
|
|
||||||
Date = DateTime.Now.Subtract(TimeSpan.FromSeconds(392)),
|
|
||||||
DestUrl = "http://bbbbb.me",
|
|
||||||
Token = "ds2"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
src/Web/WebhookClient/SessionKeys.cs
Normal file
12
src/Web/WebhookClient/SessionKeys.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebhookClient
|
||||||
|
{
|
||||||
|
static class SessionKeys
|
||||||
|
{
|
||||||
|
public const string HooksKey = "webhooks.received";
|
||||||
|
}
|
||||||
|
}
|
@ -13,5 +13,7 @@ namespace WebhookClient
|
|||||||
public string WebhooksUrl { get; set; }
|
public string WebhooksUrl { get; set; }
|
||||||
public string SelfUrl { get; set; }
|
public string SelfUrl { get; set; }
|
||||||
|
|
||||||
|
public bool ValidateToken { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,9 +59,10 @@ namespace WebhookClient
|
|||||||
{
|
{
|
||||||
if ("OPTIONS".Equals(context.Request.Method, StringComparison.InvariantCultureIgnoreCase))
|
if ("OPTIONS".Equals(context.Request.Method, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
|
var validateToken = bool.TrueString.Equals(Configuration["ValidateToken"], StringComparison.InvariantCultureIgnoreCase);
|
||||||
var header = context.Request.Headers[HeaderNames.WebHookCheckHeader];
|
var header = context.Request.Headers[HeaderNames.WebHookCheckHeader];
|
||||||
var value = header.FirstOrDefault();
|
var value = header.FirstOrDefault();
|
||||||
if (value == Configuration["Token"])
|
if (!validateToken || value == Configuration["Token"])
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user