38 changed files with 0 additions and 2045 deletions
@ -1,16 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.HttpApi.Client\LINGYUN.Abp.WebhooksManagement.HttpApi.Client.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Webhooks\LINGYUN.Abp.Webhooks.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -1,10 +0,0 @@ |
|||
using LINGYUN.Abp.WebhooksManagement; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.ClientProxies; |
|||
|
|||
[DependsOn(typeof(AbpWebhooksModule))] |
|||
[DependsOn(typeof(WebhooksManagementHttpApiClientModule))] |
|||
public class AbpWebHooksClientProxiesModule : AbpModule |
|||
{ |
|||
} |
|||
@ -1,91 +0,0 @@ |
|||
using LINGYUN.Abp.WebhooksManagement; |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.ClientProxies; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
public class ClientProxiesWebhookPublisher : IWebhookPublisher, ITransientDependency |
|||
{ |
|||
protected IWebhookPublishAppService PublishAppService { get; } |
|||
|
|||
public ClientProxiesWebhookPublisher( |
|||
IWebhookPublishAppService publishAppService) |
|||
{ |
|||
PublishAppService = publishAppService; |
|||
} |
|||
|
|||
public async virtual Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null) |
|||
{ |
|||
var input = new WebhookPublishInput |
|||
{ |
|||
WebhookName = webhookName, |
|||
Data = JsonConvert.SerializeObject(data), |
|||
SendExactSameData = sendExactSameData, |
|||
}; |
|||
if (headers != null) |
|||
{ |
|||
input.Header = new WebhooksHeaderInput |
|||
{ |
|||
UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, |
|||
Headers = headers.Headers |
|||
}; |
|||
} |
|||
|
|||
await PublishAsync(input); |
|||
} |
|||
|
|||
public async virtual Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null) |
|||
{ |
|||
var input = new WebhookPublishInput |
|||
{ |
|||
WebhookName = webhookName, |
|||
Data = JsonConvert.SerializeObject(data), |
|||
SendExactSameData = sendExactSameData, |
|||
TenantIds = new List<Guid?> |
|||
{ |
|||
tenantId |
|||
}, |
|||
}; |
|||
if (headers != null) |
|||
{ |
|||
input.Header = new WebhooksHeaderInput |
|||
{ |
|||
UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, |
|||
Headers = headers.Headers |
|||
}; |
|||
} |
|||
|
|||
await PublishAsync(input); |
|||
} |
|||
|
|||
public async virtual Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null) |
|||
{ |
|||
var input = new WebhookPublishInput |
|||
{ |
|||
WebhookName = webhookName, |
|||
Data = JsonConvert.SerializeObject(data), |
|||
SendExactSameData = sendExactSameData, |
|||
TenantIds = tenantIds.ToList(), |
|||
}; |
|||
if (headers != null) |
|||
{ |
|||
input.Header = new WebhooksHeaderInput |
|||
{ |
|||
UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, |
|||
Headers = headers.Headers |
|||
}; |
|||
} |
|||
|
|||
await PublishAsync(input); |
|||
} |
|||
|
|||
protected virtual async Task PublishAsync(WebhookPublishInput input) |
|||
{ |
|||
await PublishAppService.PublishAsync(input); |
|||
} |
|||
} |
|||
@ -1,3 +0,0 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -1,30 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -1,18 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Features" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Guids" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Http.Client" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -1,54 +0,0 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks; |
|||
|
|||
//[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))]
|
|||
// 防止未引用实现无法发布到后台作业
|
|||
[DependsOn(typeof(AbpBackgroundJobsModule))] |
|||
[DependsOn(typeof(AbpFeaturesModule))] |
|||
[DependsOn(typeof(AbpGuidsModule))] |
|||
[DependsOn(typeof(AbpHttpClientModule))] |
|||
public class AbpWebhooksModule : AbpModule |
|||
{ |
|||
internal const string WebhooksClient = "__Abp_Webhooks_HttpClient"; |
|||
|
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
AutoAddDefinitionProviders(context.Services); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var options = context.Services.ExecutePreConfiguredActions<AbpWebhooksOptions>(); |
|||
|
|||
context.Services.AddHttpClient(WebhooksClient, client => |
|||
{ |
|||
client.Timeout = options.TimeoutDuration; |
|||
}); |
|||
} |
|||
|
|||
private static void AutoAddDefinitionProviders(IServiceCollection services) |
|||
{ |
|||
var definitionProviders = new List<Type>(); |
|||
|
|||
services.OnRegistred(context => |
|||
{ |
|||
if (typeof(WebhookDefinitionProvider).IsAssignableFrom(context.ImplementationType)) |
|||
{ |
|||
definitionProviders.Add(context.ImplementationType); |
|||
} |
|||
}); |
|||
|
|||
services.Configure<AbpWebhooksOptions>(options => |
|||
{ |
|||
options.DefinitionProviders.AddIfNotContains(definitionProviders); |
|||
}); |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
using System; |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks; |
|||
|
|||
public class AbpWebhooksOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 默认超时时间
|
|||
/// </summary>
|
|||
public TimeSpan TimeoutDuration { get; set; } |
|||
/// <summary>
|
|||
/// 默认最大发送次数
|
|||
/// </summary>
|
|||
public int MaxSendAttemptCount { get; set; } |
|||
/// <summary>
|
|||
/// 是否达到最大连续失败次数时自动取消订阅
|
|||
/// </summary>
|
|||
public bool IsAutomaticSubscriptionDeactivationEnabled { get; set; } |
|||
/// <summary>
|
|||
/// 取消订阅前最大连续失败次数
|
|||
/// </summary>
|
|||
public int MaxConsecutiveFailCountBeforeDeactivateSubscription { get; set; } |
|||
|
|||
public ITypeList<WebhookDefinitionProvider> DefinitionProviders { get; } |
|||
|
|||
public AbpWebhooksOptions() |
|||
{ |
|||
TimeoutDuration = TimeSpan.FromSeconds(60); |
|||
MaxSendAttemptCount = 5; |
|||
MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3; |
|||
|
|||
DefinitionProviders = new TypeList<WebhookDefinitionProvider>(); |
|||
} |
|||
} |
|||
@ -1,131 +0,0 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.BackgroundWorker |
|||
{ |
|||
public class WebhookSenderJob : AsyncBackgroundJob<WebhookSenderArgs>, ITransientDependency |
|||
{ |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
private readonly IWebhookDefinitionManager _webhookDefinitionManager; |
|||
private readonly IWebhookSubscriptionManager _webhookSubscriptionManager; |
|||
private readonly IWebhookSendAttemptStore _webhookSendAttemptStore; |
|||
private readonly IWebhookSender _webhookSender; |
|||
|
|||
private readonly AbpWebhooksOptions _options; |
|||
|
|||
public WebhookSenderJob( |
|||
IUnitOfWorkManager unitOfWorkManager, |
|||
IWebhookDefinitionManager webhookDefinitionManager, |
|||
IWebhookSubscriptionManager webhookSubscriptionManager, |
|||
IWebhookSendAttemptStore webhookSendAttemptStore, |
|||
IWebhookSender webhookSender, |
|||
IOptions<AbpWebhooksOptions> options) |
|||
{ |
|||
_unitOfWorkManager = unitOfWorkManager; |
|||
_webhookDefinitionManager = webhookDefinitionManager; |
|||
_webhookSubscriptionManager = webhookSubscriptionManager; |
|||
_webhookSendAttemptStore = webhookSendAttemptStore; |
|||
_webhookSender = webhookSender; |
|||
_options = options.Value; |
|||
} |
|||
|
|||
public override async Task ExecuteAsync(WebhookSenderArgs args) |
|||
{ |
|||
var webhookDefinition = _webhookDefinitionManager.Get(args.WebhookName); |
|||
|
|||
if (webhookDefinition.TryOnce) |
|||
{ |
|||
try |
|||
{ |
|||
await SendWebhook(args, webhookDefinition); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.LogWarning("An error occured while sending webhook with try once.", e); |
|||
// ignored
|
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await SendWebhook(args, webhookDefinition); |
|||
} |
|||
} |
|||
|
|||
private async Task SendWebhook(WebhookSenderArgs args, WebhookDefinition webhookDefinition) |
|||
{ |
|||
if (args.WebhookEventId == default) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (args.WebhookSubscriptionId == default) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!webhookDefinition.TryOnce) |
|||
{ |
|||
var sendAttemptCount = await _webhookSendAttemptStore.GetSendAttemptCountAsync( |
|||
args.TenantId, |
|||
args.WebhookEventId, |
|||
args.WebhookSubscriptionId |
|||
); |
|||
|
|||
if ((webhookDefinition.MaxSendAttemptCount > 0 && sendAttemptCount > webhookDefinition.MaxSendAttemptCount) || |
|||
sendAttemptCount > _options.MaxSendAttemptCount) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
try |
|||
{ |
|||
await _webhookSender.SendWebhookAsync(args); |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
// no need to retry to send webhook since subscription disabled
|
|||
if (!await TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount( |
|||
args.TenantId, |
|||
args.WebhookSubscriptionId)) |
|||
{ |
|||
throw; //Throw exception to re-try sending webhook
|
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task<bool> TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount( |
|||
Guid? tenantId, |
|||
Guid subscriptionId) |
|||
{ |
|||
if (!_options.IsAutomaticSubscriptionDeactivationEnabled) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var hasXConsecutiveFail = await _webhookSendAttemptStore |
|||
.HasXConsecutiveFailAsync( |
|||
tenantId, |
|||
subscriptionId, |
|||
_options.MaxConsecutiveFailCountBeforeDeactivateSubscription |
|||
); |
|||
|
|||
if (!hasXConsecutiveFail) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
await _webhookSubscriptionManager.ActivateWebhookSubscriptionAsync(subscriptionId, false); |
|||
await uow.CompleteAsync(); |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,140 +0,0 @@ |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class DefaultWebhookPublisher : IWebhookPublisher, ITransientDependency |
|||
{ |
|||
public IWebhookEventStore WebhookEventStore { get; set; } |
|||
|
|||
private readonly ICurrentTenant _currentTenant; |
|||
private readonly IBackgroundJobManager _backgroundJobManager; |
|||
private readonly IWebhookSubscriptionManager _webhookSubscriptionManager; |
|||
|
|||
public DefaultWebhookPublisher( |
|||
IWebhookSubscriptionManager webhookSubscriptionManager, |
|||
ICurrentTenant currentTenant, |
|||
IBackgroundJobManager backgroundJobManager) |
|||
{ |
|||
_currentTenant = currentTenant; |
|||
_backgroundJobManager = backgroundJobManager; |
|||
_webhookSubscriptionManager = webhookSubscriptionManager; |
|||
|
|||
WebhookEventStore = NullWebhookEventStore.Instance; |
|||
} |
|||
|
|||
#region Async Publish Methods
|
|||
|
|||
public virtual async Task PublishAsync( |
|||
string webhookName, |
|||
object data, |
|||
bool sendExactSameData = false, |
|||
WebhookHeader headers = null) |
|||
{ |
|||
var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(_currentTenant.Id, webhookName); |
|||
await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); |
|||
} |
|||
|
|||
public virtual async Task PublishAsync( |
|||
string webhookName, |
|||
object data, |
|||
Guid? tenantId, |
|||
bool sendExactSameData = false, |
|||
WebhookHeader headers = null) |
|||
{ |
|||
var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(tenantId, webhookName); |
|||
await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); |
|||
} |
|||
|
|||
public virtual async Task PublishAsync( |
|||
Guid?[] tenantIds, |
|||
string webhookName, |
|||
object data, |
|||
bool sendExactSameData = false, |
|||
WebhookHeader headers = null) |
|||
{ |
|||
var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(tenantIds, webhookName); |
|||
await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); |
|||
} |
|||
|
|||
protected virtual async Task PublishAsync( |
|||
string webhookName, |
|||
object data, |
|||
List<WebhookSubscriptionInfo> webhookSubscriptions, |
|||
bool sendExactSameData = false, |
|||
WebhookHeader headers = null) |
|||
{ |
|||
if (webhookSubscriptions.IsNullOrEmpty()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var subscriptionsGroupedByTenant = webhookSubscriptions.GroupBy(x => x.TenantId); |
|||
|
|||
foreach (var subscriptionGroupedByTenant in subscriptionsGroupedByTenant) |
|||
{ |
|||
var webhookInfo = await SaveAndGetWebhookAsync(subscriptionGroupedByTenant.Key, webhookName, data); |
|||
|
|||
foreach (var webhookSubscription in subscriptionGroupedByTenant) |
|||
{ |
|||
var headersToSend = webhookSubscription.Headers; |
|||
if (headers != null) |
|||
{ |
|||
if (headers.UseOnlyGivenHeaders)//do not use the headers defined in subscription
|
|||
{ |
|||
headersToSend = headers.Headers; |
|||
} |
|||
else |
|||
{ |
|||
//use the headers defined in subscription. If additional headers has same header, use additional headers value.
|
|||
foreach (var additionalHeader in headers.Headers) |
|||
{ |
|||
headersToSend[additionalHeader.Key] = additionalHeader.Value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
await _backgroundJobManager.EnqueueAsync(new WebhookSenderArgs |
|||
{ |
|||
TenantId = webhookSubscription.TenantId, |
|||
WebhookEventId = webhookInfo.Id, |
|||
Data = webhookInfo.Data, |
|||
WebhookName = webhookInfo.WebhookName, |
|||
WebhookSubscriptionId = webhookSubscription.Id, |
|||
Headers = headersToSend, |
|||
Secret = webhookSubscription.Secret, |
|||
WebhookUri = webhookSubscription.WebhookUri, |
|||
SendExactSameData = sendExactSameData |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
protected virtual async Task<WebhookEvent> SaveAndGetWebhookAsync( |
|||
Guid? tenantId, |
|||
string webhookName, |
|||
object data) |
|||
{ |
|||
var webhookInfo = new WebhookEvent |
|||
{ |
|||
WebhookName = webhookName, |
|||
Data = JsonConvert.SerializeObject(data), |
|||
TenantId = tenantId |
|||
}; |
|||
|
|||
var webhookId = await WebhookEventStore.InsertAndGetIdAsync(webhookInfo); |
|||
webhookInfo.Id = webhookId; |
|||
|
|||
return webhookInfo; |
|||
} |
|||
} |
|||
} |
|||
@ -1,129 +0,0 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using System; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class DefaultWebhookSender : IWebhookSender, ITransientDependency |
|||
{ |
|||
public ILogger<DefaultWebhookSender> Logger { protected get; set; } |
|||
|
|||
private readonly IWebhookManager _webhookManager; |
|||
private readonly IHttpClientFactory _httpClientFactory; |
|||
|
|||
private const string FailedRequestDefaultContent = "Webhook Send Request Failed"; |
|||
|
|||
public DefaultWebhookSender( |
|||
IWebhookManager webhookManager, |
|||
IHttpClientFactory httpClientFactory) |
|||
{ |
|||
_webhookManager = webhookManager; |
|||
_httpClientFactory = httpClientFactory; |
|||
|
|||
Logger = NullLogger<DefaultWebhookSender>.Instance; |
|||
} |
|||
|
|||
public async Task<Guid> SendWebhookAsync(WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
if (webhookSenderArgs.WebhookEventId == default) |
|||
{ |
|||
throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookEventId)); |
|||
} |
|||
|
|||
if (webhookSenderArgs.WebhookSubscriptionId == default) |
|||
{ |
|||
throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookSubscriptionId)); |
|||
} |
|||
|
|||
var webhookSendAttemptId = await _webhookManager.InsertAndGetIdWebhookSendAttemptAsync(webhookSenderArgs); |
|||
|
|||
var request = CreateWebhookRequestMessage(webhookSenderArgs); |
|||
|
|||
var serializedBody = await _webhookManager.GetSerializedBodyAsync(webhookSenderArgs); |
|||
|
|||
_webhookManager.SignWebhookRequest(request, serializedBody, webhookSenderArgs.Secret); |
|||
|
|||
AddAdditionalHeaders(request, webhookSenderArgs); |
|||
|
|||
var isSucceed = false; |
|||
HttpStatusCode? statusCode = null; |
|||
var content = FailedRequestDefaultContent; |
|||
|
|||
try |
|||
{ |
|||
var response = await SendHttpRequest(request); |
|||
isSucceed = response.isSucceed; |
|||
statusCode = response.statusCode; |
|||
content = response.content; |
|||
} |
|||
catch (TaskCanceledException) |
|||
{ |
|||
statusCode = HttpStatusCode.RequestTimeout; |
|||
content = "Request Timeout"; |
|||
} |
|||
catch (HttpRequestException e) |
|||
{ |
|||
content = e.Message; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.LogError("An error occured while sending a webhook request", e); |
|||
} |
|||
finally |
|||
{ |
|||
await _webhookManager.StoreResponseOnWebhookSendAttemptAsync(webhookSendAttemptId, webhookSenderArgs.TenantId, statusCode, content); |
|||
} |
|||
|
|||
if (!isSucceed) |
|||
{ |
|||
throw new Exception($"Webhook sending attempt failed. WebhookSendAttempt id: {webhookSendAttemptId}"); |
|||
} |
|||
|
|||
return webhookSendAttemptId; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// You can override this to change request message
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected virtual HttpRequestMessage CreateWebhookRequestMessage(WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
return new HttpRequestMessage(HttpMethod.Post, webhookSenderArgs.WebhookUri); |
|||
} |
|||
|
|||
protected virtual void AddAdditionalHeaders(HttpRequestMessage request, WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
foreach (var header in webhookSenderArgs.Headers) |
|||
{ |
|||
if (request.Headers.TryAddWithoutValidation(header.Key, header.Value)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
throw new Exception($"Invalid Header. SubscriptionId:{webhookSenderArgs.WebhookSubscriptionId},Header: {header.Key}:{header.Value}"); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<(bool isSucceed, HttpStatusCode statusCode, string content)> SendHttpRequest(HttpRequestMessage request) |
|||
{ |
|||
var client = _httpClientFactory.CreateClient(AbpWebhooksModule.WebhooksClient); |
|||
|
|||
var response = await client.SendAsync(request); |
|||
|
|||
var isSucceed = response.IsSuccessStatusCode; |
|||
var statusCode = response.StatusCode; |
|||
var content = await response.Content.ReadAsStringAsync(); |
|||
|
|||
return (isSucceed, statusCode, content); |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Extensions |
|||
{ |
|||
public static class WebhookSubscriptionExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// checks if subscribed to given webhook
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public static bool IsSubscribed(this WebhookSubscriptionInfo webhookSubscription, string webhookName) |
|||
{ |
|||
if (webhookSubscription.Webhooks.IsNullOrEmpty()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return webhookSubscription.Webhooks.Contains(webhookName); |
|||
} |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookDefinitionContext |
|||
{ |
|||
WebhookGroupDefinition AddGroup( |
|||
[NotNull] string name, |
|||
ILocalizableString displayName = null); |
|||
|
|||
WebhookGroupDefinition GetGroupOrNull(string name); |
|||
|
|||
void RemoveGroup(string name); |
|||
} |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookDefinitionManager |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a webhook definition by name.
|
|||
/// Returns null if there is no webhook definition with given name.
|
|||
/// </summary>
|
|||
WebhookDefinition GetOrNull(string name); |
|||
|
|||
/// <summary>
|
|||
/// Gets a webhook definition by name.
|
|||
/// Throws exception if there is no webhook definition with given name.
|
|||
/// </summary>
|
|||
WebhookDefinition Get(string name); |
|||
|
|||
/// <summary>
|
|||
/// Gets all webhook definitions.
|
|||
/// </summary>
|
|||
IReadOnlyList<WebhookDefinition> GetAll(); |
|||
|
|||
/// <summary>
|
|||
/// Gets all webhook group definitions.
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
IReadOnlyList<WebhookGroupDefinition> GetGroups(); |
|||
|
|||
/// <summary>
|
|||
/// Checks if given webhook name is available for given tenant.
|
|||
/// </summary>
|
|||
Task<bool> IsAvailableAsync(Guid? tenantId, string name); |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookEventStore |
|||
{ |
|||
/// <summary>
|
|||
/// Inserts to persistent store
|
|||
/// </summary>
|
|||
Task<Guid> InsertAndGetIdAsync(WebhookEvent webhookEvent); |
|||
|
|||
/// <summary>
|
|||
/// Gets Webhook info by id
|
|||
/// </summary>
|
|||
Task<WebhookEvent> GetAsync(Guid? tenantId, Guid id); |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
using System; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookManager |
|||
{ |
|||
Task<WebhookPayload> GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs); |
|||
|
|||
void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret); |
|||
|
|||
Task<string> GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs); |
|||
|
|||
Task<Guid> InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); |
|||
|
|||
Task StoreResponseOnWebhookSendAttemptAsync( |
|||
Guid webhookSendAttemptId, Guid? tenantId, |
|||
HttpStatusCode? statusCode, string content); |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookPublisher |
|||
{ |
|||
/// <summary>
|
|||
/// Sends webhooks to current tenant subscriptions (<see cref="IAbpSession.TenantId"/>). with given data, (Checks permissions)
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="data">data to send</param>
|
|||
/// <param name="sendExactSameData">
|
|||
/// True: It sends the exact same data as the parameter to clients.
|
|||
/// <para>
|
|||
/// False: It sends data in <see cref="WebhookPayload"/>. It is recommended way.
|
|||
/// </para>
|
|||
/// </param>
|
|||
/// <param name="headers">Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.</param>
|
|||
Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); |
|||
|
|||
/// <summary>
|
|||
/// Sends webhooks to given tenant's subscriptions
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="data">data to send</param>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id
|
|||
/// </param>
|
|||
/// <param name="sendExactSameData">
|
|||
/// True: It sends the exact same data as the parameter to clients.
|
|||
/// <para>
|
|||
/// False: It sends data in <see cref="WebhookPayload"/>. It is recommended way.
|
|||
/// </para>
|
|||
/// </param>
|
|||
/// <param name="headers">Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.</param>
|
|||
Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null); |
|||
|
|||
/// <summary>
|
|||
/// Sends webhooks to given tenant's subscriptions
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="data">data to send</param>
|
|||
/// <param name="tenantIds">
|
|||
/// Target tenant id(s)
|
|||
/// </param>
|
|||
/// <param name="sendExactSameData">
|
|||
/// True: It sends the exact same data as the parameter to clients.
|
|||
/// <para>
|
|||
/// False: It sends data in <see cref="WebhookPayload"/>. It is recommended way.
|
|||
/// </para>
|
|||
/// </param>
|
|||
/// <param name="headers">Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.</param>
|
|||
Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookSendAttemptStore |
|||
{ |
|||
Task<WebhookSendAttempt> GetAsync(Guid? tenantId, Guid id); |
|||
|
|||
/// <summary>
|
|||
/// Returns work item count by given web hook id and subscription id, (How many times publisher tried to send web hook)
|
|||
/// </summary>
|
|||
Task<int> GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId); |
|||
|
|||
/// <summary>
|
|||
/// Checks is there any successful webhook attempt in last <paramref name="searchCount"/> items. Should return true if there are not X number items
|
|||
/// </summary>
|
|||
Task<bool> HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount); |
|||
|
|||
Task<(int TotalCount, IReadOnlyCollection<WebhookSendAttempt> Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, int skipCount); |
|||
|
|||
Task<List<WebhookSendAttempt>> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId); |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookSender |
|||
{ |
|||
/// <summary>
|
|||
/// Tries to send webhook with given transactionId and stores process in <see cref="WebhookSendAttempt"/>
|
|||
/// Should throw exception if fails or response status not succeed
|
|||
/// </summary>
|
|||
/// <param name="webhookSenderArgs">arguments</param>
|
|||
/// <returns>Webhook send attempt id</returns>
|
|||
Task<Guid> SendWebhookAsync(WebhookSenderArgs webhookSenderArgs); |
|||
} |
|||
} |
|||
@ -1,74 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookSubscriptionManager |
|||
{ |
|||
/// <summary>
|
|||
/// Returns subscription for given id.
|
|||
/// </summary>
|
|||
/// <param name="id">Unique identifier of <see cref="WebhookSubscriptionInfo"/></param>
|
|||
Task<WebhookSubscriptionInfo> GetAsync(Guid id); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions of tenant
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id.
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions for given webhook.
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id.
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions of tenant
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions for given webhook.
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="tenantIds">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// Checks if tenant subscribed for a webhook. (Checks if webhook features are granted)
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// If id is the default(Guid) adds new subscription, else updates current one. (Checks if webhook features are granted)
|
|||
/// </summary>
|
|||
Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription); |
|||
|
|||
/// <summary>
|
|||
/// Activates/Deactivates given webhook subscription
|
|||
/// </summary>
|
|||
/// <param name="id">unique identifier of <see cref="WebhookSubscriptionInfo"/></param>
|
|||
/// <param name="active">IsActive</param>
|
|||
Task ActivateWebhookSubscriptionAsync(Guid id, bool active); |
|||
|
|||
/// <summary>
|
|||
/// Delete given webhook subscription.
|
|||
/// </summary>
|
|||
/// <param name="id">unique identifier of <see cref="WebhookSubscriptionInfo"/></param>
|
|||
Task DeleteSubscriptionAsync(Guid id); |
|||
} |
|||
} |
|||
@ -1,83 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// This interface should be implemented by vendors to make webhooks working.
|
|||
/// </summary>
|
|||
public interface IWebhookSubscriptionsStore |
|||
{ |
|||
/// <summary>
|
|||
/// returns subscription
|
|||
/// </summary>
|
|||
/// <param name="id">webhook subscription id</param>
|
|||
/// <returns></returns>
|
|||
Task<WebhookSubscriptionInfo> GetAsync(Guid id); |
|||
|
|||
/// <summary>
|
|||
/// Saves webhook subscription to a persistent store.
|
|||
/// </summary>
|
|||
/// <param name="webhookSubscription">webhook subscription information</param>
|
|||
Task InsertAsync(WebhookSubscriptionInfo webhookSubscription); |
|||
|
|||
/// <summary>
|
|||
/// Updates webhook subscription to a persistent store.
|
|||
/// </summary>
|
|||
/// <param name="webhookSubscription">webhook subscription information</param>
|
|||
Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription); |
|||
|
|||
/// <summary>
|
|||
/// Deletes subscription if exists
|
|||
/// </summary>
|
|||
/// <param name="id"><see cref="WebhookSubscriptionInfo"/> primary key</param>
|
|||
/// <returns></returns>
|
|||
Task DeleteAsync(Guid id); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions of given tenant including deactivated
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id.
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId); |
|||
|
|||
/// <summary>
|
|||
/// Returns webhook subscriptions which subscribe to given webhook on tenant(s)
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id.
|
|||
/// </param>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <returns></returns>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions of given tenant including deactivated
|
|||
/// </summary>
|
|||
/// <param name="tenantIds">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); |
|||
|
|||
/// <summary>
|
|||
/// Returns webhook subscriptions which subscribe to given webhook on tenant(s)
|
|||
/// </summary>
|
|||
/// <param name="tenantIds">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <returns></returns>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// Checks if tenant subscribed for a webhook
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
/// <param name="webhookName">Name of the webhook</param>
|
|||
Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName); |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// Null pattern implementation of <see cref="IWebhookSubscriptionsStore"/>.
|
|||
/// It's used if <see cref="IWebhookSubscriptionsStore"/> is not implemented by actual persistent store
|
|||
/// </summary>
|
|||
public class NullWebhookEventStore : IWebhookEventStore |
|||
{ |
|||
public static NullWebhookEventStore Instance { get; } = new NullWebhookEventStore(); |
|||
|
|||
public Task<Guid> InsertAndGetIdAsync(WebhookEvent webhookEvent) |
|||
{ |
|||
return Task.FromResult<Guid>(default); |
|||
} |
|||
|
|||
public Task<WebhookEvent> GetAsync(Guid? tenantId, Guid id) |
|||
{ |
|||
return Task.FromResult<WebhookEvent>(default); |
|||
} |
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class NullWebhookSendAttemptStore : IWebhookSendAttemptStore |
|||
{ |
|||
public static NullWebhookSendAttemptStore Instance = new NullWebhookSendAttemptStore(); |
|||
|
|||
public Task InsertAsync(WebhookSendAttempt webhookSendAttempt) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task UpdateAsync(WebhookSendAttempt webhookSendAttempt) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task<WebhookSendAttempt> GetAsync(Guid? tenantId, Guid id) |
|||
{ |
|||
return Task.FromResult<WebhookSendAttempt>(default); |
|||
} |
|||
|
|||
public Task<int> GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId) |
|||
{ |
|||
return Task.FromResult(int.MaxValue); |
|||
} |
|||
|
|||
public Task<bool> HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
public Task<(int TotalCount, IReadOnlyCollection<WebhookSendAttempt> Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, |
|||
int skipCount) |
|||
{ |
|||
return Task.FromResult(ValueTuple.Create(0, new List<WebhookSendAttempt>() as IReadOnlyCollection<WebhookSendAttempt>)); |
|||
} |
|||
|
|||
public Task<List<WebhookSendAttempt>> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSendAttempt>()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,65 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// Null pattern implementation of <see cref="IWebhookSubscriptionsStore"/>.
|
|||
/// It's used if <see cref="IWebhookSubscriptionsStore"/> is not implemented by actual persistent store
|
|||
/// </summary>
|
|||
public class NullWebhookSubscriptionsStore : IWebhookSubscriptionsStore |
|||
{ |
|||
public static NullWebhookSubscriptionsStore Instance { get; } = new NullWebhookSubscriptionsStore(); |
|||
|
|||
public Task<WebhookSubscriptionInfo> GetAsync(Guid id) |
|||
{ |
|||
return Task.FromResult<WebhookSubscriptionInfo>(default); |
|||
} |
|||
|
|||
public WebhookSubscriptionInfo Get(Guid id) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
public Task InsertAsync(WebhookSubscriptionInfo webhookSubscription) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task DeleteAsync(Guid id) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSubscriptionInfo>()); |
|||
} |
|||
|
|||
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSubscriptionInfo>()); |
|||
} |
|||
|
|||
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSubscriptionInfo>()); |
|||
} |
|||
|
|||
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSubscriptionInfo>()); |
|||
} |
|||
|
|||
public Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
return Task.FromResult(false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,67 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookDefinition |
|||
{ |
|||
/// <summary>
|
|||
/// Unique name of the webhook.
|
|||
/// </summary>
|
|||
public string Name { get; } |
|||
|
|||
/// <summary>
|
|||
/// Tries to send webhook only one time without checking to send attempt count
|
|||
/// </summary>
|
|||
public bool TryOnce { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Defined maximum number of sending times
|
|||
/// </summary>
|
|||
public int MaxSendAttemptCount { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Display name of the webhook.
|
|||
/// Optional.
|
|||
/// </summary>
|
|||
public ILocalizableString DisplayName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Description for the webhook.
|
|||
/// Optional.
|
|||
/// </summary>
|
|||
public ILocalizableString Description { get; set; } |
|||
|
|||
public List<string> RequiredFeatures { get; set; } |
|||
|
|||
public WebhookDefinition(string name, ILocalizableString displayName = null, ILocalizableString description = null) |
|||
{ |
|||
if (name.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new ArgumentNullException(nameof(name), $"{nameof(name)} can not be null, empty or whitespace!"); |
|||
} |
|||
|
|||
Name = name.Trim(); |
|||
DisplayName = displayName; |
|||
Description = description; |
|||
|
|||
RequiredFeatures = new List<string>(); |
|||
} |
|||
|
|||
public WebhookDefinition WithFeature(params string[] features) |
|||
{ |
|||
if (!features.IsNullOrEmpty()) |
|||
{ |
|||
RequiredFeatures.AddRange(features); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"[{nameof(WebhookDefinition)} {Name}]"; |
|||
} |
|||
} |
|||
} |
|||
@ -1,55 +0,0 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookDefinitionContext : IWebhookDefinitionContext |
|||
{ |
|||
protected Dictionary<string, WebhookGroupDefinition> Groups { get; } |
|||
|
|||
public WebhookDefinitionContext(Dictionary<string, WebhookGroupDefinition> webhooks) |
|||
{ |
|||
Groups = webhooks; |
|||
} |
|||
|
|||
public WebhookGroupDefinition AddGroup( |
|||
[NotNull] string name, |
|||
ILocalizableString displayName = null) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (Groups.ContainsKey(name)) |
|||
{ |
|||
throw new AbpException($"There is already an existing webhook group with name: {name}"); |
|||
} |
|||
|
|||
return Groups[name] = new WebhookGroupDefinition(name, displayName); |
|||
} |
|||
|
|||
public WebhookGroupDefinition GetGroupOrNull([NotNull] string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (!Groups.ContainsKey(name)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return Groups[name]; |
|||
} |
|||
|
|||
public void RemoveGroup(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (!Groups.ContainsKey(name)) |
|||
{ |
|||
throw new AbpException($"Undefined notification webhook group: '{name}'."); |
|||
} |
|||
|
|||
Groups.Remove(name); |
|||
} |
|||
} |
|||
} |
|||
@ -1,139 +0,0 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonDependency |
|||
{ |
|||
protected IDictionary<string, WebhookGroupDefinition> WebhookGroupDefinitions => _lazyWebhookGroupDefinitions.Value; |
|||
private readonly Lazy<Dictionary<string, WebhookGroupDefinition>> _lazyWebhookGroupDefinitions; |
|||
|
|||
protected IDictionary<string, WebhookDefinition> WebhookDefinitions => _lazyWebhookDefinitions.Value; |
|||
private readonly Lazy<Dictionary<string, WebhookDefinition>> _lazyWebhookDefinitions; |
|||
|
|||
private readonly IServiceProvider _serviceProvider; |
|||
private readonly AbpWebhooksOptions _options; |
|||
|
|||
public WebhookDefinitionManager( |
|||
IServiceProvider serviceProvider, |
|||
IOptions<AbpWebhooksOptions> options) |
|||
{ |
|||
_serviceProvider = serviceProvider; |
|||
_options = options.Value; |
|||
|
|||
_lazyWebhookGroupDefinitions = new Lazy<Dictionary<string, WebhookGroupDefinition>>(CreateWebhookGroupDefinitions); |
|||
_lazyWebhookDefinitions = new Lazy<Dictionary<string, WebhookDefinition>>(CreateWebhookDefinitions); |
|||
} |
|||
|
|||
public WebhookDefinition GetOrNull(string name) |
|||
{ |
|||
if (!WebhookDefinitions.ContainsKey(name)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return WebhookDefinitions[name]; |
|||
} |
|||
|
|||
public WebhookDefinition Get(string name) |
|||
{ |
|||
if (!WebhookDefinitions.ContainsKey(name)) |
|||
{ |
|||
throw new KeyNotFoundException($"Webhook definitions does not contain a definition with the key \"{name}\"."); |
|||
} |
|||
|
|||
return WebhookDefinitions[name]; |
|||
} |
|||
|
|||
public IReadOnlyList<WebhookDefinition> GetAll() |
|||
{ |
|||
return WebhookDefinitions.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public IReadOnlyList<WebhookGroupDefinition> GetGroups() |
|||
{ |
|||
return WebhookGroupDefinitions.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public async Task<bool> IsAvailableAsync(Guid? tenantId, string name) |
|||
{ |
|||
if (tenantId == null) // host allowed to subscribe all webhooks
|
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
var webhookDefinition = GetOrNull(name); |
|||
|
|||
if (webhookDefinition == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (webhookDefinition.RequiredFeatures?.Any() == false) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
var currentTenant = _serviceProvider.GetRequiredService<ICurrentTenant>(); |
|||
var featureChecker = _serviceProvider.GetRequiredService<IFeatureChecker>(); |
|||
using (currentTenant.Change(tenantId)) |
|||
{ |
|||
if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray())) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
protected virtual Dictionary<string, WebhookDefinition> CreateWebhookDefinitions() |
|||
{ |
|||
var definitions = new Dictionary<string, WebhookDefinition>(); |
|||
|
|||
foreach (var groupDefinition in WebhookGroupDefinitions.Values) |
|||
{ |
|||
foreach (var webhook in groupDefinition.Webhooks) |
|||
{ |
|||
if (definitions.ContainsKey(webhook.Name)) |
|||
{ |
|||
throw new AbpException("Duplicate webhook name: " + webhook.Name); |
|||
} |
|||
|
|||
definitions[webhook.Name] = webhook; |
|||
} |
|||
} |
|||
|
|||
return definitions; |
|||
} |
|||
|
|||
protected virtual Dictionary<string, WebhookGroupDefinition> CreateWebhookGroupDefinitions() |
|||
{ |
|||
var definitions = new Dictionary<string, WebhookGroupDefinition>(); |
|||
|
|||
using (var scope = _serviceProvider.CreateScope()) |
|||
{ |
|||
var providers = _options |
|||
.DefinitionProviders |
|||
.Select(p => scope.ServiceProvider.GetRequiredService(p) as WebhookDefinitionProvider) |
|||
.ToList(); |
|||
|
|||
foreach (var provider in providers) |
|||
{ |
|||
provider.Define(new WebhookDefinitionContext(definitions)); |
|||
} |
|||
} |
|||
|
|||
return definitions; |
|||
} |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public abstract class WebhookDefinitionProvider : ITransientDependency |
|||
{ |
|||
/// <summary>
|
|||
/// Used to add/manipulate webhook definitions.
|
|||
/// </summary>
|
|||
/// <param name="context">Context</param>,
|
|||
public abstract void Define(IWebhookDefinitionContext context); |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// Store created web hooks. To see who get that webhook check with <see cref="WebhookSendAttempt.WebhookEventId"/> and you can get <see cref="WebhookSendAttempt.WebhookSubscriptionId"/>
|
|||
/// </summary>
|
|||
public class WebhookEvent |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook unique name <see cref="WebhookDefinition.Name"/>
|
|||
/// </summary>
|
|||
public string WebhookName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook data as JSON string.
|
|||
/// </summary>
|
|||
public string Data { get; set; } |
|||
|
|||
public DateTime CreationTime { get; set; } |
|||
|
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public bool IsDeleted { get; set; } |
|||
|
|||
public DateTime? DeletionTime { get; set; } |
|||
} |
|||
} |
|||
@ -1,101 +0,0 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks; |
|||
|
|||
public class WebhookGroupDefinition |
|||
{ |
|||
[NotNull] |
|||
public string Name { get; set; } |
|||
public Dictionary<string, object> Properties { get; } |
|||
|
|||
private ILocalizableString _displayName; |
|||
public ILocalizableString DisplayName |
|||
{ |
|||
get { |
|||
return _displayName; |
|||
} |
|||
set { |
|||
_displayName = value; |
|||
} |
|||
} |
|||
|
|||
public IReadOnlyList<WebhookDefinition> Webhooks => _webhooks.ToImmutableList(); |
|||
private readonly List<WebhookDefinition> _webhooks; |
|||
public object this[string name] { |
|||
get => Properties.GetOrDefault(name); |
|||
set => Properties[name] = value; |
|||
} |
|||
|
|||
protected internal WebhookGroupDefinition( |
|||
string name, |
|||
ILocalizableString displayName = null) |
|||
{ |
|||
Name = name; |
|||
DisplayName = displayName ?? new FixedLocalizableString(Name); |
|||
|
|||
Properties = new Dictionary<string, object>(); |
|||
_webhooks = new List<WebhookDefinition>(); |
|||
} |
|||
|
|||
public virtual WebhookDefinition AddWebhook( |
|||
string name, |
|||
ILocalizableString displayName = null, |
|||
ILocalizableString description = null) |
|||
{ |
|||
if (Webhooks.Any(hook => hook.Name.Equals(name))) |
|||
{ |
|||
throw new AbpException($"There is already an existing webhook with name: {name} in group {Name}"); |
|||
} |
|||
|
|||
var webhook = new WebhookDefinition( |
|||
name, |
|||
displayName, |
|||
description |
|||
); |
|||
|
|||
_webhooks.Add(webhook); |
|||
|
|||
return webhook; |
|||
} |
|||
|
|||
public virtual void AddWebhooks(params WebhookDefinition[] webhooks) |
|||
{ |
|||
foreach (var webhook in webhooks) |
|||
{ |
|||
if (Webhooks.Any(hook => hook.Name.Equals(webhook.Name))) |
|||
{ |
|||
throw new AbpException($"There is already an existing webhook with name: {webhook.Name} in group {Name}"); |
|||
} |
|||
} |
|||
|
|||
_webhooks.AddRange(webhooks); |
|||
} |
|||
|
|||
|
|||
|
|||
[CanBeNull] |
|||
public WebhookDefinition GetWebhookOrNull([NotNull] string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
foreach (var webhook in Webhooks) |
|||
{ |
|||
if (webhook.Name == name) |
|||
{ |
|||
return webhook; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"[{nameof(WebhookGroupDefinition)} {Name}]"; |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookHeader |
|||
{ |
|||
/// <summary>
|
|||
/// If true, webhook will only contain given headers. If false given headers will be added to predefined headers in subscription.
|
|||
/// Default is false
|
|||
/// </summary>
|
|||
public bool UseOnlyGivenHeaders { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// That headers will be sent with the webhook.
|
|||
/// </summary>
|
|||
public IDictionary<string, string> Headers { get; set; } |
|||
} |
|||
} |
|||
@ -1,80 +0,0 @@ |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public abstract class WebhookManager : IWebhookManager |
|||
{ |
|||
private const string SignatureHeaderKey = "sha256"; |
|||
private const string SignatureHeaderValueTemplate = SignatureHeaderKey + "={0}"; |
|||
private const string SignatureHeaderName = "abp-webhook-signature"; |
|||
protected IWebhookSendAttemptStore WebhookSendAttemptStore { get; } |
|||
|
|||
protected WebhookManager( |
|||
IWebhookSendAttemptStore webhookSendAttemptStore) |
|||
{ |
|||
WebhookSendAttemptStore = webhookSendAttemptStore; |
|||
} |
|||
|
|||
public virtual async Task<WebhookPayload> GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
var data = JsonConvert.SerializeObject(webhookSenderArgs.Data); |
|||
|
|||
var attemptNumber = await WebhookSendAttemptStore.GetSendAttemptCountAsync( |
|||
webhookSenderArgs.TenantId, |
|||
webhookSenderArgs.WebhookEventId, |
|||
webhookSenderArgs.WebhookSubscriptionId); |
|||
|
|||
return new WebhookPayload( |
|||
webhookSenderArgs.WebhookEventId.ToString(), |
|||
webhookSenderArgs.WebhookName, |
|||
attemptNumber) |
|||
{ |
|||
Data = data |
|||
}; |
|||
} |
|||
|
|||
public virtual void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret) |
|||
{ |
|||
if (request == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(request)); |
|||
} |
|||
|
|||
if (string.IsNullOrWhiteSpace(serializedBody)) |
|||
{ |
|||
throw new ArgumentNullException(nameof(serializedBody)); |
|||
} |
|||
|
|||
request.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json"); |
|||
|
|||
var secretBytes = Encoding.UTF8.GetBytes(secret); |
|||
var headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, serializedBody.Sha256(secretBytes)); |
|||
|
|||
request.Headers.Add(SignatureHeaderName, headerValue); |
|||
} |
|||
|
|||
public virtual async Task<string> GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
if (webhookSenderArgs.SendExactSameData) |
|||
{ |
|||
return webhookSenderArgs.Data; |
|||
} |
|||
|
|||
var payload = await GetWebhookPayloadAsync(webhookSenderArgs); |
|||
|
|||
var serializedBody = JsonConvert.SerializeObject(payload); |
|||
|
|||
return serializedBody; |
|||
} |
|||
|
|||
public abstract Task<Guid> InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); |
|||
|
|||
public abstract Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content); |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookPayload |
|||
{ |
|||
public string Id { get; set; } |
|||
|
|||
public string WebhookEvent { get; set; } |
|||
|
|||
public int Attempt { get; set; } |
|||
|
|||
public dynamic Data { get; set; } |
|||
|
|||
public DateTime CreationTimeUtc { get; set; } |
|||
|
|||
public WebhookPayload(string id, string webhookEvent, int attempt) |
|||
{ |
|||
if (id.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new ArgumentNullException(nameof(id)); |
|||
} |
|||
|
|||
if (webhookEvent.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new ArgumentNullException(nameof(webhookEvent)); |
|||
} |
|||
|
|||
Id = id; |
|||
WebhookEvent = webhookEvent; |
|||
Attempt = attempt; |
|||
CreationTimeUtc = DateTime.UtcNow; |
|||
} |
|||
} |
|||
} |
|||
@ -1,39 +0,0 @@ |
|||
using System; |
|||
using System.Net; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// Table for store webhook work items. Each item stores web hook send attempt of <see cref="WebhookEvent"/> to subscribed tenants
|
|||
/// </summary>
|
|||
public class WebhookSendAttempt |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// <see cref="WebhookEvent"/> foreign id
|
|||
/// </summary>
|
|||
public Guid WebhookEventId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// <see cref="WebhookSubscription"/> foreign id
|
|||
/// </summary>
|
|||
public Guid WebhookSubscriptionId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook response content that webhook endpoint send back
|
|||
/// </summary>
|
|||
public string Response { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook response status code that webhook endpoint send back
|
|||
/// </summary>
|
|||
public HttpStatusCode? ResponseStatusCode { get; set; } |
|||
|
|||
public DateTime CreationTime { get; set; } |
|||
|
|||
public DateTime? LastModificationTime { get; set; } |
|||
|
|||
public Guid? TenantId { get; set; } |
|||
} |
|||
} |
|||
@ -1,62 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookSenderArgs |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
//Webhook information
|
|||
|
|||
/// <summary>
|
|||
/// <see cref="WebhookEvent"/> foreign id
|
|||
/// </summary>
|
|||
public Guid WebhookEventId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook unique name
|
|||
/// </summary>
|
|||
public string WebhookName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook data as JSON string.
|
|||
/// </summary>
|
|||
public string Data { get; set; } |
|||
|
|||
//Subscription information
|
|||
|
|||
/// <summary>
|
|||
/// <see cref="WebhookSubscription"/> foreign id
|
|||
/// </summary>
|
|||
public Guid WebhookSubscriptionId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Subscription webhook endpoint
|
|||
/// </summary>
|
|||
public string WebhookUri { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook secret
|
|||
/// </summary>
|
|||
public string Secret { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a set of additional HTTP headers.That headers will be sent with the webhook.
|
|||
/// </summary>
|
|||
public IDictionary<string, string> Headers { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// True: It sends the exact same data as the parameter to clients.
|
|||
/// <para>
|
|||
/// False: It sends data in <see cref="WebhookPayload"/>. It is recommended way.
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public bool SendExactSameData { get; set; } |
|||
|
|||
public WebhookSenderArgs() |
|||
{ |
|||
Headers = new Dictionary<string, string>(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,60 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookSubscriptionInfo |
|||
{ |
|||
public Guid Id { get; set; } |
|||
/// <summary>
|
|||
/// Subscribed Tenant's id .
|
|||
/// </summary>
|
|||
public Guid? TenantId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Subscription webhook endpoint
|
|||
/// </summary>
|
|||
public string WebhookUri { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook secret
|
|||
/// </summary>
|
|||
public string Secret { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Is subscription active
|
|||
/// </summary>
|
|||
public bool IsActive { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Subscribed webhook definitions unique names.It contains webhook definitions list as json
|
|||
/// <para>
|
|||
/// Do not change it manually.
|
|||
/// Use <see cref=" WebhookSubscriptionInfoExtensions.GetSubscribedWebhooks"/>,
|
|||
/// <see cref=" WebhookSubscriptionInfoExtensions.SubscribeWebhook"/>,
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.UnsubscribeWebhook"/> and
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.RemoveAllSubscribedWebhooks"/> to change it.
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public List<string> Webhooks { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a set of additional HTTP headers.That headers will be sent with the webhook. It contains webhook header dictionary as json
|
|||
/// <para>
|
|||
/// Do not change it manually.
|
|||
/// Use <see cref=" WebhookSubscriptionInfoExtensions.GetWebhookHeaders"/>,
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.AddWebhookHeader"/>,
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.RemoveWebhookHeader"/>,
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.RemoveAllWebhookHeaders"/> to change it.
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public IDictionary<string, string> Headers { get; set; } |
|||
|
|||
public WebhookSubscriptionInfo() |
|||
{ |
|||
IsActive = true; |
|||
Headers = new Dictionary<string, string>(); |
|||
Webhooks = new List<string>(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,172 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Authorization; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookSubscriptionManager : IWebhookSubscriptionManager, ITransientDependency |
|||
{ |
|||
public IWebhookSubscriptionsStore WebhookSubscriptionsStore { get; set; } |
|||
|
|||
private readonly IGuidGenerator _guidGenerator; |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
private readonly IWebhookDefinitionManager _webhookDefinitionManager; |
|||
|
|||
private const string WebhookSubscriptionSecretPrefix = "whs_"; |
|||
|
|||
public WebhookSubscriptionManager( |
|||
IGuidGenerator guidGenerator, |
|||
IUnitOfWorkManager unitOfWorkManager, |
|||
IWebhookDefinitionManager webhookDefinitionManager) |
|||
{ |
|||
_guidGenerator = guidGenerator; |
|||
_unitOfWorkManager = unitOfWorkManager; |
|||
_webhookDefinitionManager = webhookDefinitionManager; |
|||
|
|||
WebhookSubscriptionsStore = NullWebhookSubscriptionsStore.Instance; |
|||
} |
|||
|
|||
public virtual async Task<WebhookSubscriptionInfo> GetAsync(Guid id) |
|||
{ |
|||
return await WebhookSubscriptionsStore.GetAsync(id); |
|||
} |
|||
|
|||
public virtual async Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId) |
|||
{ |
|||
return await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId); |
|||
} |
|||
|
|||
public virtual async Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) |
|||
{ |
|||
return new List<WebhookSubscriptionInfo>(); |
|||
} |
|||
|
|||
return (await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId, webhookName)).ToList(); |
|||
} |
|||
|
|||
public virtual async Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) |
|||
{ |
|||
return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(tenantIds)).ToList(); |
|||
} |
|||
|
|||
public virtual async Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName) |
|||
{ |
|||
var featureGrantedTenants = new List<Guid?>(); |
|||
foreach (var tenantId in tenantIds) |
|||
{ |
|||
if (await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) |
|||
{ |
|||
featureGrantedTenants.Add(tenantId); |
|||
} |
|||
} |
|||
|
|||
return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(featureGrantedTenants.ToArray(), webhookName)).ToList(); |
|||
} |
|||
|
|||
public virtual async Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return await WebhookSubscriptionsStore.IsSubscribedAsync(tenantId, webhookName); |
|||
} |
|||
|
|||
public virtual async Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription) |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
await CheckIfPermissionsGrantedAsync(webhookSubscription); |
|||
|
|||
if (webhookSubscription.Id == default) |
|||
{ |
|||
webhookSubscription.Id = _guidGenerator.Create(); |
|||
webhookSubscription.Secret = WebhookSubscriptionSecretPrefix + Guid.NewGuid().ToString("N"); |
|||
await WebhookSubscriptionsStore.InsertAsync(webhookSubscription); |
|||
} |
|||
else |
|||
{ |
|||
await WebhookSubscriptionsStore.UpdateAsync(webhookSubscription); |
|||
} |
|||
|
|||
await uow.SaveChangesAsync(); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task ActivateWebhookSubscriptionAsync(Guid id, bool active) |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
var webhookSubscription = await WebhookSubscriptionsStore.GetAsync(id); |
|||
webhookSubscription.IsActive = active; |
|||
|
|||
await uow.SaveChangesAsync(); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task DeleteSubscriptionAsync(Guid id) |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
await WebhookSubscriptionsStore.DeleteAsync(id); |
|||
|
|||
await uow.SaveChangesAsync(); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task AddWebhookAsync(WebhookSubscriptionInfo subscription, string webhookName) |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
await CheckPermissionsAsync(subscription.TenantId, webhookName); |
|||
webhookName = webhookName.Trim(); |
|||
if (webhookName.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new ArgumentNullException(nameof(webhookName), $"{nameof(webhookName)} can not be null, empty or whitespace!"); |
|||
} |
|||
|
|||
if (!subscription.Webhooks.Contains(webhookName)) |
|||
{ |
|||
subscription.Webhooks.Add(webhookName); |
|||
|
|||
await WebhookSubscriptionsStore.UpdateAsync(subscription); |
|||
} |
|||
|
|||
await uow.SaveChangesAsync(); |
|||
} |
|||
} |
|||
|
|||
#region PermissionCheck
|
|||
|
|||
protected virtual async Task CheckIfPermissionsGrantedAsync(WebhookSubscriptionInfo webhookSubscription) |
|||
{ |
|||
if (webhookSubscription.Webhooks.IsNullOrEmpty()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var webhookDefinition in webhookSubscription.Webhooks) |
|||
{ |
|||
await CheckPermissionsAsync(webhookSubscription.TenantId, webhookDefinition); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task CheckPermissionsAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) |
|||
{ |
|||
throw new AbpAuthorizationException($"Tenant \"{tenantId}\" must have necessary feature(s) to use webhook \"{webhookName}\""); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
using System.Security.Cryptography; |
|||
|
|||
namespace System; |
|||
|
|||
internal static class AbpStringCryptographyExtensions |
|||
{ |
|||
public static string Sha256(this string planText, byte[] salt) |
|||
{ |
|||
var data = planText.GetBytes(); |
|||
using var hmacsha256 = new HMACSHA256(salt); |
|||
return BitConverter.ToString(hmacsha256.ComputeHash(data)); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue