Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebHooks Module #2323

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion OrchardCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Cms.Templates",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Logging.Serilog", "src\OrchardCore\OrchardCore.Logging.Serilog\OrchardCore.Logging.Serilog.csproj", "{B9DC28CB-F531-4533-B53E-6599970BF3F8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.MiniProfiler", "src\OrchardCore.Modules\OrchardCore.MiniProfiler\OrchardCore.MiniProfiler.csproj", "{261E038A-25DA-4DD7-A66A-5884C6F2BEBF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.MiniProfiler", "src\OrchardCore.Modules\OrchardCore.MiniProfiler\OrchardCore.MiniProfiler.csproj", "{261E038A-25DA-4DD7-A66A-5884C6F2BEBF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.WebHooks", "src\OrchardCore.Modules\OrchardCore.WebHooks\OrchardCore.WebHooks.csproj", "{D51E6846-B325-4F99-BC69-72F3D38BBFFE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.WebHooks.Abstractions", "src\OrchardCore\OrchardCore.WebHooks.Abstractions\OrchardCore.WebHooks.Abstractions.csproj", "{749D76B3-9BA6-4CFD-9CC5-F52FB11AB74B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -925,6 +929,14 @@ Global
{261E038A-25DA-4DD7-A66A-5884C6F2BEBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{261E038A-25DA-4DD7-A66A-5884C6F2BEBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{261E038A-25DA-4DD7-A66A-5884C6F2BEBF}.Release|Any CPU.Build.0 = Release|Any CPU
{D51E6846-B325-4F99-BC69-72F3D38BBFFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D51E6846-B325-4F99-BC69-72F3D38BBFFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D51E6846-B325-4F99-BC69-72F3D38BBFFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D51E6846-B325-4F99-BC69-72F3D38BBFFE}.Release|Any CPU.Build.0 = Release|Any CPU
{749D76B3-9BA6-4CFD-9CC5-F52FB11AB74B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{749D76B3-9BA6-4CFD-9CC5-F52FB11AB74B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{749D76B3-9BA6-4CFD-9CC5-F52FB11AB74B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{749D76B3-9BA6-4CFD-9CC5-F52FB11AB74B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1087,6 +1099,8 @@ Global
{6EF610C3-EC5A-45CC-BDCC-0159B6D2D15A} = {FA7416DC-50F5-4576-9166-78B5129DABA9}
{B9DC28CB-F531-4533-B53E-6599970BF3F8} = {F23AC6C2-DE44-4699-999D-3C478EF3D691}
{261E038A-25DA-4DD7-A66A-5884C6F2BEBF} = {A066395F-6F73-45DC-B5A6-B4E306110DCE}
{D51E6846-B325-4F99-BC69-72F3D38BBFFE} = {90030E85-0C4F-456F-B879-443E8A3F220D}
{749D76B3-9BA6-4CFD-9CC5-F52FB11AB74B} = {F23AC6C2-DE44-4699-999D-3C478EF3D691}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46A1D25A-78D1-4476-9CBF-25B75E296341}
Expand Down
1 change: 1 addition & 0 deletions src/OrchardCore.Build/Dependencies.AspNetCore.props
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
<PackageManagement Include="Microsoft.Extensions.FileProviders.Physical" Version="2.1.1" />
<PackageManagement Include="Microsoft.Extensions.FileSystemGlobbing" Version="2.1.1" />
<PackageManagement Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.1.1" />
<PackageManagement Include="Microsoft.Extensions.Http" Version="2.1.1" />
<PackageManagement Include="Microsoft.Extensions.Http.Polly" Version="2.1.1" />
<PackageManagement Include="Microsoft.Extensions.Identity.Core" Version="2.1.3" />
<PackageManagement Include="Microsoft.Extensions.Identity.Stores" Version="2.1.3" />
Expand Down
34 changes: 34 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.WebHooks/AdminMenu.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using Microsoft.Extensions.Localization;
using OrchardCore.Environment.Navigation;

namespace OrchardCore.WebHooks
{
public class AdminMenu : INavigationProvider
{
public AdminMenu(IStringLocalizer<AdminMenu> localizer)
{
T = localizer;
}

public IStringLocalizer T { get; set; }

public void BuildNavigation(string name, NavigationBuilder builder)
{
if (!String.Equals(name, "admin", StringComparison.OrdinalIgnoreCase))
{
return;
}

builder
.Add(T["Configuration"], "10", configuration => configuration
.AddClass("menu-configuration").Id("configuration")
.Add(T["Webhooks"], "8", webhooks => webhooks
.Action("Index", "WebHook", new { area = "OrchardCore.WebHooks" })
.Permission(Permissions.ManageWebHooks)
.LocalNav()
)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
using System;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using OrchardCore.Admin;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.WebHooks.Abstractions.Services;
using OrchardCore.WebHooks.ViewModels;

namespace OrchardCore.WebHooks.Controllers
{
[Admin]
public class WebHookController : Controller
{
private readonly IAuthorizationService _authorizationService;
private readonly IWebHookStore _store;
private readonly IWebHookEventManager _eventManager;
private readonly INotifier _notifier;

public WebHookController(
IWebHookStore store,
IWebHookEventManager eventManager,
IAuthorizationService authorizationService,
INotifier notifier,
IStringLocalizer<WebHookController> stringLocalizer,
IHtmlLocalizer<WebHookController> htmlLocalizer,
ILogger<WebHookController> logger
)
{
_store = store;
_eventManager = eventManager;
_authorizationService = authorizationService;
_notifier = notifier;

S = stringLocalizer;
H = htmlLocalizer;
Logger = logger;
}

public IStringLocalizer S { get; set; }

public IHtmlLocalizer H { get; set; }

public ILogger<WebHookController> Logger { get; }

public async Task<IActionResult> Index()
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageWebHooks))
{
return Unauthorized();
}

var webHooksList = await _store.GetAllWebHooksAsync();

var model = new WebHookIndexViewModel
{
WebHooksList = webHooksList
};

return View(model);
}

public async Task<IActionResult> Create()
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageWebHooks))
{
return Unauthorized();
}

var events = await _eventManager.GetAllWebHookEventsAsync();
var model = new EditWebHookViewModel
{
Events = events
};

return View(model);
}

[HttpPost]
public async Task<IActionResult> Create(EditWebHookViewModel model)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageWebHooks))
{
return Unauthorized();
}

if (ModelState.IsValid)
{
await ProcessWebHookAsync(model);
await _store.CreateWebHookAsync(model.WebHook);

_notifier.Success(H["Webhook created successfully"]);
return RedirectToAction(nameof(Index));
}

// If we got this far, something failed, redisplay form
model.Events = await _eventManager.GetAllWebHookEventsAsync();
return View(model);
}

public async Task<IActionResult> Edit(string id)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageWebHooks))
{
return Unauthorized();
}

var webHook = await _store.GetWebHookAsync(id);

if (webHook == null)
{
return NotFound();
}

var events = await _eventManager.GetAllWebHookEventsAsync();
var model = new EditWebHookViewModel
{
Events = events,
WebHook = webHook,
CustomPayload = webHook.PayloadTemplate != null,
SubscribeAllEvents = webHook.Events.Contains("*")
};

return View(model);
}

[HttpPost]
public async Task<IActionResult> Edit(EditWebHookViewModel model)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageWebHooks))
{
return Unauthorized();
}

var webHook = await _store.GetWebHookAsync(model.WebHook.Id);

if (webHook == null)
{
return NotFound();
}

if (ModelState.IsValid)
{
await ProcessWebHookAsync(model);
await _store.TryUpdateWebHook(model.WebHook);

_notifier.Success(H["Webhook updated successfully"]);

return RedirectToAction(nameof(Index));
}

// If we got this far, something failed, redisplay form
model.Events = await _eventManager.GetAllWebHookEventsAsync();
return View(model);
}

[HttpPost]
public async Task<IActionResult> Delete(string id)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageWebHooks))
{
return Unauthorized();
}

var webHook = await _store.GetWebHookAsync(id);

if (webHook == null)
{
return NotFound();
}

await _store.DeleteWebHookAsync(id);

_notifier.Success(H["Webhook deleted successfully"]);

return RedirectToAction(nameof(Index));
}

[HttpGet]
public async Task<IActionResult> Ping(string id)
{
if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageWebHooks))
{
return Unauthorized();
}

var webHook = await _store.GetWebHookAsync(id);

if (webHook == null)
{
return NotFound();
}

var ping = new Ping();
var uri = new Uri(webHook.Url);

PingReply result = null;
string errorMessage = string.Empty;
try
{
result = ping.Send(uri.Host);
}
catch (PingException ex)
{
errorMessage = ex.Message;
Logger.LogInformation(ex, "Failed to ping {0} due to failure: {1}.", uri.Host, errorMessage);
}

if (result == null || result.Status != IPStatus.Success)
{
_notifier.Error(H["Failed to ping {0}. {1}", uri.Host, errorMessage]);
}
else
{
_notifier.Success(H["Successfully pinged {0}", uri.Host]);
}

return RedirectToAction(nameof(Index));
}

private async Task ProcessWebHookAsync(EditWebHookViewModel model)
{
// Clear all events so that the event provider can fallback to the wildcard event
if (model.SubscribeAllEvents)
{
model.WebHook.Events.Clear();
}

// Clear the custom payload template if we aren't using it
if (!model.CustomPayload)
{
model.WebHook.PayloadTemplate = null;
}

// Remove empty custom headers
model.WebHook.Headers = model.WebHook.Headers.Where(header => !string.IsNullOrWhiteSpace(header.Key)).ToList();

// Validate and optimize the webhook's event subscriptions
model.WebHook.Events = await _eventManager.NormalizeEventsAsync(model.WebHook.Events);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using OrchardCore.WebHooks.Abstractions.Models;

namespace OrchardCore.WebHooks.Expressions
{
public interface IWebHookExpressionEvaluator
{
Task<JObject> RenderAsync(WebHook webHook, WebHookNotificationContext context);
}
}
Loading