From 711b9e0edf9c7dcd3e7700dafb428fa7a416134a Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Tue, 5 Apr 2022 11:05:58 +0800 Subject: [PATCH 1/2] fix: fix case in reference project paths --- .../LINGYUN.Abp.WebHooks.ClientProxies.csproj | 16 -- .../AbpWebHooksClientProxiesModule.cs | 10 - .../ClientProxiesWebhookPublisher.cs | 91 --------- .../LINGYUN.Abp.WebHooks/FodyWeavers.xml | 3 - .../LINGYUN.Abp.WebHooks/FodyWeavers.xsd | 30 --- .../LINGYUN.Abp.WebHooks.csproj | 18 -- .../LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs | 54 ------ .../Abp/Webhooks/AbpWebhooksOptions.cs | 35 ---- .../BackgroundWorker/WebhookSenderJob.cs | 131 ------------- .../Abp/Webhooks/DefaultWebhookPublisher.cs | 140 -------------- .../Abp/Webhooks/DefaultWebhookSender.cs | 129 ------------- .../WebhookSubscriptionExtensions.cs | 21 --- .../Abp/Webhooks/IWebhookDefinitionContext.cs | 16 -- .../Abp/Webhooks/IWebhookDefinitionManager.cs | 37 ---- .../Abp/Webhooks/IWebhookEventStore.cs | 18 -- .../LINGYUN/Abp/Webhooks/IWebhookManager.cs | 22 --- .../LINGYUN/Abp/Webhooks/IWebhookPublisher.cs | 56 ------ .../Abp/Webhooks/IWebhookSendAttemptStore.cs | 25 --- .../LINGYUN/Abp/Webhooks/IWebhookSender.cs | 16 -- .../Webhooks/IWebhookSubscriptionManager.cs | 74 -------- .../Webhooks/IWebhookSubscriptionsStore.cs | 83 --------- .../Abp/Webhooks/NullWebhookEventStore.cs | 24 --- .../Webhooks/NullWebhookSendAttemptStore.cs | 47 ----- .../Webhooks/NullWebhookSubscriptionsStore.cs | 65 ------- .../LINGYUN/Abp/Webhooks/WebhookDefinition.cs | 67 ------- .../Abp/Webhooks/WebhookDefinitionContext.cs | 55 ------ .../Abp/Webhooks/WebhookDefinitionManager.cs | 139 -------------- .../Abp/Webhooks/WebhookDefinitionProvider.cs | 13 -- .../LINGYUN/Abp/Webhooks/WebhookEvent.cs | 30 --- .../Abp/Webhooks/WebhookGroupDefinition.cs | 101 ---------- .../LINGYUN/Abp/Webhooks/WebhookHeader.cs | 18 -- .../LINGYUN/Abp/Webhooks/WebhookManager.cs | 80 -------- .../LINGYUN/Abp/Webhooks/WebhookPayload.cs | 35 ---- .../Abp/Webhooks/WebhookSendAttempt.cs | 39 ---- .../LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs | 62 ------- .../Abp/Webhooks/WebhookSubscriptionInfo.cs | 60 ------ .../Webhooks/WebhookSubscriptionManager.cs | 172 ------------------ .../System/AbpStringCryptographyExtensions.cs | 13 -- 38 files changed, 2045 deletions(-) delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs delete mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj deleted file mode 100644 index 251dd53d3..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - netstandard2.0 - - - - - - - - - diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs deleted file mode 100644 index 41e08a4a7..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs +++ /dev/null @@ -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 -{ -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs deleted file mode 100644 index 9e5ae8b35..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs +++ /dev/null @@ -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 - { - 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); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml deleted file mode 100644 index 17d32672d..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd deleted file mode 100644 index 11da52550..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj deleted file mode 100644 index 2cd64673f..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - netstandard2.0 - - - - - - - - - - - diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs deleted file mode 100644 index 84798d8d2..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs +++ /dev/null @@ -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(); - - context.Services.AddHttpClient(WebhooksClient, client => - { - client.Timeout = options.TimeoutDuration; - }); - } - - private static void AutoAddDefinitionProviders(IServiceCollection services) - { - var definitionProviders = new List(); - - services.OnRegistred(context => - { - if (typeof(WebhookDefinitionProvider).IsAssignableFrom(context.ImplementationType)) - { - definitionProviders.Add(context.ImplementationType); - } - }); - - services.Configure(options => - { - options.DefinitionProviders.AddIfNotContains(definitionProviders); - }); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs deleted file mode 100644 index 072a76bcc..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Volo.Abp.Collections; - -namespace LINGYUN.Abp.Webhooks; - -public class AbpWebhooksOptions -{ - /// - /// 默认超时时间 - /// - public TimeSpan TimeoutDuration { get; set; } - /// - /// 默认最大发送次数 - /// - public int MaxSendAttemptCount { get; set; } - /// - /// 是否达到最大连续失败次数时自动取消订阅 - /// - public bool IsAutomaticSubscriptionDeactivationEnabled { get; set; } - /// - /// 取消订阅前最大连续失败次数 - /// - public int MaxConsecutiveFailCountBeforeDeactivateSubscription { get; set; } - - public ITypeList DefinitionProviders { get; } - - public AbpWebhooksOptions() - { - TimeoutDuration = TimeSpan.FromSeconds(60); - MaxSendAttemptCount = 5; - MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3; - - DefinitionProviders = new TypeList(); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs deleted file mode 100644 index b411e3a84..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs +++ /dev/null @@ -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, 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 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 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; - } - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs deleted file mode 100644 index be46197ae..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs +++ /dev/null @@ -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 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 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; - } - } -} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs deleted file mode 100644 index 19982a68e..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs +++ /dev/null @@ -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 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.Instance; - } - - public async Task 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; - } - - /// - /// You can override this to change request message - /// - /// - 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); - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs deleted file mode 100644 index 8cf48f520..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; - -namespace LINGYUN.Abp.Webhooks.Extensions -{ - public static class WebhookSubscriptionExtensions - { - /// - /// checks if subscribed to given webhook - /// - /// - public static bool IsSubscribed(this WebhookSubscriptionInfo webhookSubscription, string webhookName) - { - if (webhookSubscription.Webhooks.IsNullOrEmpty()) - { - return false; - } - - return webhookSubscription.Webhooks.Contains(webhookName); - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs deleted file mode 100644 index c170ede3d..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs +++ /dev/null @@ -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); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs deleted file mode 100644 index a284ab693..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - public interface IWebhookDefinitionManager - { - /// - /// Gets a webhook definition by name. - /// Returns null if there is no webhook definition with given name. - /// - WebhookDefinition GetOrNull(string name); - - /// - /// Gets a webhook definition by name. - /// Throws exception if there is no webhook definition with given name. - /// - WebhookDefinition Get(string name); - - /// - /// Gets all webhook definitions. - /// - IReadOnlyList GetAll(); - - /// - /// Gets all webhook group definitions. - /// - /// - IReadOnlyList GetGroups(); - - /// - /// Checks if given webhook name is available for given tenant. - /// - Task IsAvailableAsync(Guid? tenantId, string name); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs deleted file mode 100644 index 1fb8f5c21..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - public interface IWebhookEventStore - { - /// - /// Inserts to persistent store - /// - Task InsertAndGetIdAsync(WebhookEvent webhookEvent); - - /// - /// Gets Webhook info by id - /// - Task GetAsync(Guid? tenantId, Guid id); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs deleted file mode 100644 index 1e8e5fd0c..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs +++ /dev/null @@ -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 GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs); - - void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret); - - Task GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs); - - Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); - - Task StoreResponseOnWebhookSendAttemptAsync( - Guid webhookSendAttemptId, Guid? tenantId, - HttpStatusCode? statusCode, string content); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs deleted file mode 100644 index 3b53fec83..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - public interface IWebhookPublisher - { - /// - /// Sends webhooks to current tenant subscriptions (). with given data, (Checks permissions) - /// - /// - /// data to send - /// - /// True: It sends the exact same data as the parameter to clients. - /// - /// False: It sends data in . It is recommended way. - /// - /// - /// 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. - Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); - - /// - /// Sends webhooks to given tenant's subscriptions - /// - /// - /// data to send - /// - /// Target tenant id - /// - /// - /// True: It sends the exact same data as the parameter to clients. - /// - /// False: It sends data in . It is recommended way. - /// - /// - /// 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. - Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null); - - /// - /// Sends webhooks to given tenant's subscriptions - /// - /// - /// data to send - /// - /// Target tenant id(s) - /// - /// - /// True: It sends the exact same data as the parameter to clients. - /// - /// False: It sends data in . It is recommended way. - /// - /// - /// 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. - Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); - } -} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs deleted file mode 100644 index 2e32bc63f..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - public interface IWebhookSendAttemptStore - { - Task GetAsync(Guid? tenantId, Guid id); - - /// - /// Returns work item count by given web hook id and subscription id, (How many times publisher tried to send web hook) - /// - Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId); - - /// - /// Checks is there any successful webhook attempt in last items. Should return true if there are not X number items - /// - Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount); - - Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, int skipCount); - - Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs deleted file mode 100644 index d30e110e6..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - public interface IWebhookSender - { - /// - /// Tries to send webhook with given transactionId and stores process in - /// Should throw exception if fails or response status not succeed - /// - /// arguments - /// Webhook send attempt id - Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs deleted file mode 100644 index 956b2dbd3..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - public interface IWebhookSubscriptionManager - { - /// - /// Returns subscription for given id. - /// - /// Unique identifier of - Task GetAsync(Guid id); - - /// - /// Returns all subscriptions of tenant - /// - /// - /// Target tenant id. - /// - Task> GetAllSubscriptionsAsync(Guid? tenantId); - - /// - /// Returns all subscriptions for given webhook. - /// - /// - /// - /// Target tenant id. - /// - Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName); - - /// - /// Returns all subscriptions of tenant - /// - /// - Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); - - /// - /// Returns all subscriptions for given webhook. - /// - /// - /// - /// Target tenant id(s). - /// - Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName); - - /// - /// Checks if tenant subscribed for a webhook. (Checks if webhook features are granted) - /// - /// - /// Target tenant id(s). - /// - /// - Task IsSubscribedAsync(Guid? tenantId, string webhookName); - - /// - /// If id is the default(Guid) adds new subscription, else updates current one. (Checks if webhook features are granted) - /// - Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription); - - /// - /// Activates/Deactivates given webhook subscription - /// - /// unique identifier of - /// IsActive - Task ActivateWebhookSubscriptionAsync(Guid id, bool active); - - /// - /// Delete given webhook subscription. - /// - /// unique identifier of - Task DeleteSubscriptionAsync(Guid id); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs deleted file mode 100644 index 23849ad4e..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - /// - /// This interface should be implemented by vendors to make webhooks working. - /// - public interface IWebhookSubscriptionsStore - { - /// - /// returns subscription - /// - /// webhook subscription id - /// - Task GetAsync(Guid id); - - /// - /// Saves webhook subscription to a persistent store. - /// - /// webhook subscription information - Task InsertAsync(WebhookSubscriptionInfo webhookSubscription); - - /// - /// Updates webhook subscription to a persistent store. - /// - /// webhook subscription information - Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription); - - /// - /// Deletes subscription if exists - /// - /// primary key - /// - Task DeleteAsync(Guid id); - - /// - /// Returns all subscriptions of given tenant including deactivated - /// - /// - /// Target tenant id. - /// - Task> GetAllSubscriptionsAsync(Guid? tenantId); - - /// - /// Returns webhook subscriptions which subscribe to given webhook on tenant(s) - /// - /// - /// Target tenant id. - /// - /// - /// - Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName); - - /// - /// Returns all subscriptions of given tenant including deactivated - /// - /// - /// Target tenant id(s). - /// - Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); - - /// - /// Returns webhook subscriptions which subscribe to given webhook on tenant(s) - /// - /// - /// Target tenant id(s). - /// - /// - /// - Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName); - - /// - /// Checks if tenant subscribed for a webhook - /// - /// - /// Target tenant id(s). - /// - /// Name of the webhook - Task IsSubscribedAsync(Guid? tenantId, string webhookName); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs deleted file mode 100644 index 069726e67..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - /// - /// Null pattern implementation of . - /// It's used if is not implemented by actual persistent store - /// - public class NullWebhookEventStore : IWebhookEventStore - { - public static NullWebhookEventStore Instance { get; } = new NullWebhookEventStore(); - - public Task InsertAndGetIdAsync(WebhookEvent webhookEvent) - { - return Task.FromResult(default); - } - - public Task GetAsync(Guid? tenantId, Guid id) - { - return Task.FromResult(default); - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs deleted file mode 100644 index 7254b1ae6..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs +++ /dev/null @@ -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 GetAsync(Guid? tenantId, Guid id) - { - return Task.FromResult(default); - } - - public Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId) - { - return Task.FromResult(int.MaxValue); - } - - public Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount) - { - return default; - } - - public Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, - int skipCount) - { - return Task.FromResult(ValueTuple.Create(0, new List() as IReadOnlyCollection)); - } - - public Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId) - { - return Task.FromResult(new List()); - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs deleted file mode 100644 index 4214d1d25..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Webhooks -{ - /// - /// Null pattern implementation of . - /// It's used if is not implemented by actual persistent store - /// - public class NullWebhookSubscriptionsStore : IWebhookSubscriptionsStore - { - public static NullWebhookSubscriptionsStore Instance { get; } = new NullWebhookSubscriptionsStore(); - - public Task GetAsync(Guid id) - { - return Task.FromResult(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> GetAllSubscriptionsAsync(Guid? tenantId) - { - return Task.FromResult(new List()); - } - - public Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName) - { - return Task.FromResult(new List()); - } - - public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) - { - return Task.FromResult(new List()); - } - - public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName) - { - return Task.FromResult(new List()); - } - - public Task IsSubscribedAsync(Guid? tenantId, string webhookName) - { - return Task.FromResult(false); - } - } -} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs deleted file mode 100644 index 1c1461e8a..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.Webhooks -{ - public class WebhookDefinition - { - /// - /// Unique name of the webhook. - /// - public string Name { get; } - - /// - /// Tries to send webhook only one time without checking to send attempt count - /// - public bool TryOnce { get; set; } - - /// - /// Defined maximum number of sending times - /// - public int MaxSendAttemptCount { get; set; } - - /// - /// Display name of the webhook. - /// Optional. - /// - public ILocalizableString DisplayName { get; set; } - - /// - /// Description for the webhook. - /// Optional. - /// - public ILocalizableString Description { get; set; } - - public List 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(); - } - - public WebhookDefinition WithFeature(params string[] features) - { - if (!features.IsNullOrEmpty()) - { - RequiredFeatures.AddRange(features); - } - - return this; - } - - public override string ToString() - { - return $"[{nameof(WebhookDefinition)} {Name}]"; - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs deleted file mode 100644 index 5e8987a8b..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs +++ /dev/null @@ -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 Groups { get; } - - public WebhookDefinitionContext(Dictionary 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); - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs deleted file mode 100644 index c0f96f20e..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs +++ /dev/null @@ -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 WebhookGroupDefinitions => _lazyWebhookGroupDefinitions.Value; - private readonly Lazy> _lazyWebhookGroupDefinitions; - - protected IDictionary WebhookDefinitions => _lazyWebhookDefinitions.Value; - private readonly Lazy> _lazyWebhookDefinitions; - - private readonly IServiceProvider _serviceProvider; - private readonly AbpWebhooksOptions _options; - - public WebhookDefinitionManager( - IServiceProvider serviceProvider, - IOptions options) - { - _serviceProvider = serviceProvider; - _options = options.Value; - - _lazyWebhookGroupDefinitions = new Lazy>(CreateWebhookGroupDefinitions); - _lazyWebhookDefinitions = new Lazy>(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 GetAll() - { - return WebhookDefinitions.Values.ToImmutableList(); - } - - public IReadOnlyList GetGroups() - { - return WebhookGroupDefinitions.Values.ToImmutableList(); - } - - public async Task 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(); - var featureChecker = _serviceProvider.GetRequiredService(); - using (currentTenant.Change(tenantId)) - { - if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray())) - { - return false; - } - } - - return true; - } - - protected virtual Dictionary CreateWebhookDefinitions() - { - var definitions = new Dictionary(); - - 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 CreateWebhookGroupDefinitions() - { - var definitions = new Dictionary(); - - 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; - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs deleted file mode 100644 index ead9ab679..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Volo.Abp.DependencyInjection; - -namespace LINGYUN.Abp.Webhooks -{ - public abstract class WebhookDefinitionProvider : ITransientDependency - { - /// - /// Used to add/manipulate webhook definitions. - /// - /// Context, - public abstract void Define(IWebhookDefinitionContext context); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs deleted file mode 100644 index bd32fa76d..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace LINGYUN.Abp.Webhooks -{ - /// - /// Store created web hooks. To see who get that webhook check with and you can get - /// - public class WebhookEvent - { - public Guid Id { get; set; } - - /// - /// Webhook unique name - /// - public string WebhookName { get; set; } - - /// - /// Webhook data as JSON string. - /// - 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; } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs deleted file mode 100644 index ae58049ed..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs +++ /dev/null @@ -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 Properties { get; } - - private ILocalizableString _displayName; - public ILocalizableString DisplayName - { - get { - return _displayName; - } - set { - _displayName = value; - } - } - - public IReadOnlyList Webhooks => _webhooks.ToImmutableList(); - private readonly List _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(); - _webhooks = new List(); - } - - 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}]"; - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs deleted file mode 100644 index 91442552f..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; - -namespace LINGYUN.Abp.Webhooks -{ - public class WebhookHeader - { - /// - /// If true, webhook will only contain given headers. If false given headers will be added to predefined headers in subscription. - /// Default is false - /// - public bool UseOnlyGivenHeaders { get; set; } - - /// - /// That headers will be sent with the webhook. - /// - public IDictionary Headers { get; set; } - } -} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs deleted file mode 100644 index 854795786..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs +++ /dev/null @@ -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 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 GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs) - { - if (webhookSenderArgs.SendExactSameData) - { - return webhookSenderArgs.Data; - } - - var payload = await GetWebhookPayloadAsync(webhookSenderArgs); - - var serializedBody = JsonConvert.SerializeObject(payload); - - return serializedBody; - } - - public abstract Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); - - public abstract Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content); - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs deleted file mode 100644 index ff287cbd0..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs +++ /dev/null @@ -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; - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs deleted file mode 100644 index 8189f8f72..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Net; - -namespace LINGYUN.Abp.Webhooks -{ - /// - /// Table for store webhook work items. Each item stores web hook send attempt of to subscribed tenants - /// - public class WebhookSendAttempt - { - public Guid Id { get; set; } - - /// - /// foreign id - /// - public Guid WebhookEventId { get; set; } - - /// - /// foreign id - /// - public Guid WebhookSubscriptionId { get; set; } - - /// - /// Webhook response content that webhook endpoint send back - /// - public string Response { get; set; } - - /// - /// Webhook response status code that webhook endpoint send back - /// - public HttpStatusCode? ResponseStatusCode { get; set; } - - public DateTime CreationTime { get; set; } - - public DateTime? LastModificationTime { get; set; } - - public Guid? TenantId { get; set; } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs deleted file mode 100644 index cb30acbf9..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LINGYUN.Abp.Webhooks -{ - public class WebhookSenderArgs - { - public Guid? TenantId { get; set; } - - //Webhook information - - /// - /// foreign id - /// - public Guid WebhookEventId { get; set; } - - /// - /// Webhook unique name - /// - public string WebhookName { get; set; } - - /// - /// Webhook data as JSON string. - /// - public string Data { get; set; } - - //Subscription information - - /// - /// foreign id - /// - public Guid WebhookSubscriptionId { get; set; } - - /// - /// Subscription webhook endpoint - /// - public string WebhookUri { get; set; } - - /// - /// Webhook secret - /// - public string Secret { get; set; } - - /// - /// Gets a set of additional HTTP headers.That headers will be sent with the webhook. - /// - public IDictionary Headers { get; set; } - - /// - /// True: It sends the exact same data as the parameter to clients. - /// - /// False: It sends data in . It is recommended way. - /// - /// - public bool SendExactSameData { get; set; } - - public WebhookSenderArgs() - { - Headers = new Dictionary(); - } - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs deleted file mode 100644 index 9e4e3bf2a..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LINGYUN.Abp.Webhooks -{ - public class WebhookSubscriptionInfo - { - public Guid Id { get; set; } - /// - /// Subscribed Tenant's id . - /// - public Guid? TenantId { get; set; } - - /// - /// Subscription webhook endpoint - /// - public string WebhookUri { get; set; } - - /// - /// Webhook secret - /// - public string Secret { get; set; } - - /// - /// Is subscription active - /// - public bool IsActive { get; set; } - - /// - /// Subscribed webhook definitions unique names.It contains webhook definitions list as json - /// - /// Do not change it manually. - /// Use , - /// , - /// and - /// to change it. - /// - /// - public List Webhooks { get; set; } - - /// - /// Gets a set of additional HTTP headers.That headers will be sent with the webhook. It contains webhook header dictionary as json - /// - /// Do not change it manually. - /// Use , - /// , - /// , - /// to change it. - /// - /// - public IDictionary Headers { get; set; } - - public WebhookSubscriptionInfo() - { - IsActive = true; - Headers = new Dictionary(); - Webhooks = new List(); - } - } -} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs deleted file mode 100644 index fe1a9c697..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs +++ /dev/null @@ -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 GetAsync(Guid id) - { - return await WebhookSubscriptionsStore.GetAsync(id); - } - - public virtual async Task> GetAllSubscriptionsAsync(Guid? tenantId) - { - return await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId); - } - - public virtual async Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName) - { - if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) - { - return new List(); - } - - return (await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId, webhookName)).ToList(); - } - - public virtual async Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) - { - return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(tenantIds)).ToList(); - } - - public virtual async Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName) - { - var featureGrantedTenants = new List(); - 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 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 - } -} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs deleted file mode 100644 index f4202f6e2..000000000 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs +++ /dev/null @@ -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)); - } -} From ce4d8221b00010f4984dfdbeca094a70ae9a5665 Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Tue, 5 Apr 2022 11:06:49 +0800 Subject: [PATCH 2/2] fix: fix case in reference project paths --- .../LINGYUN.Abp.Webhooks.ClientProxies.csproj | 16 ++ .../AbpWebhooksClientProxiesModule.cs | 10 + .../ClientProxiesWebhookPublisher.cs | 91 +++++++++ .../LINGYUN.Abp.Webhooks/FodyWeavers.xml | 3 + .../LINGYUN.Abp.Webhooks/FodyWeavers.xsd | 30 +++ .../LINGYUN.Abp.Webhooks.csproj | 18 ++ .../LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs | 54 ++++++ .../Abp/Webhooks/AbpWebhooksOptions.cs | 35 ++++ .../BackgroundWorker/WebhookSenderJob.cs | 131 +++++++++++++ .../Abp/Webhooks/DefaultWebhookPublisher.cs | 140 ++++++++++++++ .../Abp/Webhooks/DefaultWebhookSender.cs | 129 +++++++++++++ .../WebhookSubscriptionExtensions.cs | 21 +++ .../Abp/Webhooks/IWebhookDefinitionContext.cs | 16 ++ .../Abp/Webhooks/IWebhookDefinitionManager.cs | 37 ++++ .../Abp/Webhooks/IWebhookEventStore.cs | 18 ++ .../LINGYUN/Abp/Webhooks/IWebhookManager.cs | 22 +++ .../LINGYUN/Abp/Webhooks/IWebhookPublisher.cs | 56 ++++++ .../Abp/Webhooks/IWebhookSendAttemptStore.cs | 25 +++ .../LINGYUN/Abp/Webhooks/IWebhookSender.cs | 16 ++ .../Webhooks/IWebhookSubscriptionManager.cs | 74 ++++++++ .../Webhooks/IWebhookSubscriptionsStore.cs | 83 +++++++++ .../Abp/Webhooks/NullWebhookEventStore.cs | 24 +++ .../Webhooks/NullWebhookSendAttemptStore.cs | 47 +++++ .../Webhooks/NullWebhookSubscriptionsStore.cs | 65 +++++++ .../LINGYUN/Abp/Webhooks/WebhookDefinition.cs | 67 +++++++ .../Abp/Webhooks/WebhookDefinitionContext.cs | 55 ++++++ .../Abp/Webhooks/WebhookDefinitionManager.cs | 139 ++++++++++++++ .../Abp/Webhooks/WebhookDefinitionProvider.cs | 13 ++ .../LINGYUN/Abp/Webhooks/WebhookEvent.cs | 30 +++ .../Abp/Webhooks/WebhookGroupDefinition.cs | 101 ++++++++++ .../LINGYUN/Abp/Webhooks/WebhookHeader.cs | 18 ++ .../LINGYUN/Abp/Webhooks/WebhookManager.cs | 80 ++++++++ .../LINGYUN/Abp/Webhooks/WebhookPayload.cs | 35 ++++ .../Abp/Webhooks/WebhookSendAttempt.cs | 39 ++++ .../LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs | 62 +++++++ .../Abp/Webhooks/WebhookSubscriptionInfo.cs | 60 ++++++ .../Webhooks/WebhookSubscriptionManager.cs | 172 ++++++++++++++++++ .../System/AbpStringCryptographyExtensions.cs | 13 ++ 38 files changed, 2045 insertions(+) create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN.Abp.Webhooks.ClientProxies.csproj create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/AbpWebhooksClientProxiesModule.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/ClientProxiesWebhookPublisher.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xml create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xsd create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN.Abp.Webhooks.csproj create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookManager.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs create mode 100644 aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/System/AbpStringCryptographyExtensions.cs diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN.Abp.Webhooks.ClientProxies.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN.Abp.Webhooks.ClientProxies.csproj new file mode 100644 index 000000000..251dd53d3 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN.Abp.Webhooks.ClientProxies.csproj @@ -0,0 +1,16 @@ + + + + + + + netstandard2.0 + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/AbpWebhooksClientProxiesModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/AbpWebhooksClientProxiesModule.cs new file mode 100644 index 000000000..41e08a4a7 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/AbpWebhooksClientProxiesModule.cs @@ -0,0 +1,10 @@ +using LINGYUN.Abp.WebhooksManagement; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Webhooks.ClientProxies; + +[DependsOn(typeof(AbpWebhooksModule))] +[DependsOn(typeof(WebhooksManagementHttpApiClientModule))] +public class AbpWebHooksClientProxiesModule : AbpModule +{ +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/ClientProxiesWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/ClientProxiesWebhookPublisher.cs new file mode 100644 index 000000000..9e5ae8b35 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.ClientProxies/LINGYUN/Abp/Webhooks/ClientProxies/ClientProxiesWebhookPublisher.cs @@ -0,0 +1,91 @@ +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 + { + 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); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xml new file mode 100644 index 000000000..17d32672d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN.Abp.Webhooks.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN.Abp.Webhooks.csproj new file mode 100644 index 000000000..2cd64673f --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN.Abp.Webhooks.csproj @@ -0,0 +1,18 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs new file mode 100644 index 000000000..84798d8d2 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs @@ -0,0 +1,54 @@ +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(); + + context.Services.AddHttpClient(WebhooksClient, client => + { + client.Timeout = options.TimeoutDuration; + }); + } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(WebhookDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs new file mode 100644 index 000000000..072a76bcc --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs @@ -0,0 +1,35 @@ +using System; +using Volo.Abp.Collections; + +namespace LINGYUN.Abp.Webhooks; + +public class AbpWebhooksOptions +{ + /// + /// 默认超时时间 + /// + public TimeSpan TimeoutDuration { get; set; } + /// + /// 默认最大发送次数 + /// + public int MaxSendAttemptCount { get; set; } + /// + /// 是否达到最大连续失败次数时自动取消订阅 + /// + public bool IsAutomaticSubscriptionDeactivationEnabled { get; set; } + /// + /// 取消订阅前最大连续失败次数 + /// + public int MaxConsecutiveFailCountBeforeDeactivateSubscription { get; set; } + + public ITypeList DefinitionProviders { get; } + + public AbpWebhooksOptions() + { + TimeoutDuration = TimeSpan.FromSeconds(60); + MaxSendAttemptCount = 5; + MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3; + + DefinitionProviders = new TypeList(); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs new file mode 100644 index 000000000..b411e3a84 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs @@ -0,0 +1,131 @@ +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, 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 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 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; + } + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs new file mode 100644 index 000000000..be46197ae --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs @@ -0,0 +1,140 @@ +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 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 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; + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs new file mode 100644 index 000000000..19982a68e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs @@ -0,0 +1,129 @@ +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 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.Instance; + } + + public async Task 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; + } + + /// + /// You can override this to change request message + /// + /// + 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); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs new file mode 100644 index 000000000..8cf48f520 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.Webhooks.Extensions +{ + public static class WebhookSubscriptionExtensions + { + /// + /// checks if subscribed to given webhook + /// + /// + public static bool IsSubscribed(this WebhookSubscriptionInfo webhookSubscription, string webhookName) + { + if (webhookSubscription.Webhooks.IsNullOrEmpty()) + { + return false; + } + + return webhookSubscription.Webhooks.Contains(webhookName); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs new file mode 100644 index 000000000..c170ede3d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs @@ -0,0 +1,16 @@ +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); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs new file mode 100644 index 000000000..a284ab693 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookDefinitionManager + { + /// + /// Gets a webhook definition by name. + /// Returns null if there is no webhook definition with given name. + /// + WebhookDefinition GetOrNull(string name); + + /// + /// Gets a webhook definition by name. + /// Throws exception if there is no webhook definition with given name. + /// + WebhookDefinition Get(string name); + + /// + /// Gets all webhook definitions. + /// + IReadOnlyList GetAll(); + + /// + /// Gets all webhook group definitions. + /// + /// + IReadOnlyList GetGroups(); + + /// + /// Checks if given webhook name is available for given tenant. + /// + Task IsAvailableAsync(Guid? tenantId, string name); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs new file mode 100644 index 000000000..1fb8f5c21 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookEventStore + { + /// + /// Inserts to persistent store + /// + Task InsertAndGetIdAsync(WebhookEvent webhookEvent); + + /// + /// Gets Webhook info by id + /// + Task GetAsync(Guid? tenantId, Guid id); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs new file mode 100644 index 000000000..1e8e5fd0c --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs @@ -0,0 +1,22 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookManager + { + Task GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs); + + void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret); + + Task GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs); + + Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); + + Task StoreResponseOnWebhookSendAttemptAsync( + Guid webhookSendAttemptId, Guid? tenantId, + HttpStatusCode? statusCode, string content); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs new file mode 100644 index 000000000..3b53fec83 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookPublisher + { + /// + /// Sends webhooks to current tenant subscriptions (). with given data, (Checks permissions) + /// + /// + /// data to send + /// + /// True: It sends the exact same data as the parameter to clients. + /// + /// False: It sends data in . It is recommended way. + /// + /// + /// 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. + Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); + + /// + /// Sends webhooks to given tenant's subscriptions + /// + /// + /// data to send + /// + /// Target tenant id + /// + /// + /// True: It sends the exact same data as the parameter to clients. + /// + /// False: It sends data in . It is recommended way. + /// + /// + /// 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. + Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null); + + /// + /// Sends webhooks to given tenant's subscriptions + /// + /// + /// data to send + /// + /// Target tenant id(s) + /// + /// + /// True: It sends the exact same data as the parameter to clients. + /// + /// False: It sends data in . It is recommended way. + /// + /// + /// 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. + Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs new file mode 100644 index 000000000..2e32bc63f --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookSendAttemptStore + { + Task GetAsync(Guid? tenantId, Guid id); + + /// + /// Returns work item count by given web hook id and subscription id, (How many times publisher tried to send web hook) + /// + Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId); + + /// + /// Checks is there any successful webhook attempt in last items. Should return true if there are not X number items + /// + Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount); + + Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, int skipCount); + + Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs new file mode 100644 index 000000000..d30e110e6 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookSender + { + /// + /// Tries to send webhook with given transactionId and stores process in + /// Should throw exception if fails or response status not succeed + /// + /// arguments + /// Webhook send attempt id + Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs new file mode 100644 index 000000000..956b2dbd3 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookSubscriptionManager + { + /// + /// Returns subscription for given id. + /// + /// Unique identifier of + Task GetAsync(Guid id); + + /// + /// Returns all subscriptions of tenant + /// + /// + /// Target tenant id. + /// + Task> GetAllSubscriptionsAsync(Guid? tenantId); + + /// + /// Returns all subscriptions for given webhook. + /// + /// + /// + /// Target tenant id. + /// + Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName); + + /// + /// Returns all subscriptions of tenant + /// + /// + Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); + + /// + /// Returns all subscriptions for given webhook. + /// + /// + /// + /// Target tenant id(s). + /// + Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName); + + /// + /// Checks if tenant subscribed for a webhook. (Checks if webhook features are granted) + /// + /// + /// Target tenant id(s). + /// + /// + Task IsSubscribedAsync(Guid? tenantId, string webhookName); + + /// + /// If id is the default(Guid) adds new subscription, else updates current one. (Checks if webhook features are granted) + /// + Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription); + + /// + /// Activates/Deactivates given webhook subscription + /// + /// unique identifier of + /// IsActive + Task ActivateWebhookSubscriptionAsync(Guid id, bool active); + + /// + /// Delete given webhook subscription. + /// + /// unique identifier of + Task DeleteSubscriptionAsync(Guid id); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs new file mode 100644 index 000000000..23849ad4e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// This interface should be implemented by vendors to make webhooks working. + /// + public interface IWebhookSubscriptionsStore + { + /// + /// returns subscription + /// + /// webhook subscription id + /// + Task GetAsync(Guid id); + + /// + /// Saves webhook subscription to a persistent store. + /// + /// webhook subscription information + Task InsertAsync(WebhookSubscriptionInfo webhookSubscription); + + /// + /// Updates webhook subscription to a persistent store. + /// + /// webhook subscription information + Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription); + + /// + /// Deletes subscription if exists + /// + /// primary key + /// + Task DeleteAsync(Guid id); + + /// + /// Returns all subscriptions of given tenant including deactivated + /// + /// + /// Target tenant id. + /// + Task> GetAllSubscriptionsAsync(Guid? tenantId); + + /// + /// Returns webhook subscriptions which subscribe to given webhook on tenant(s) + /// + /// + /// Target tenant id. + /// + /// + /// + Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName); + + /// + /// Returns all subscriptions of given tenant including deactivated + /// + /// + /// Target tenant id(s). + /// + Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); + + /// + /// Returns webhook subscriptions which subscribe to given webhook on tenant(s) + /// + /// + /// Target tenant id(s). + /// + /// + /// + Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName); + + /// + /// Checks if tenant subscribed for a webhook + /// + /// + /// Target tenant id(s). + /// + /// Name of the webhook + Task IsSubscribedAsync(Guid? tenantId, string webhookName); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs new file mode 100644 index 000000000..069726e67 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// Null pattern implementation of . + /// It's used if is not implemented by actual persistent store + /// + public class NullWebhookEventStore : IWebhookEventStore + { + public static NullWebhookEventStore Instance { get; } = new NullWebhookEventStore(); + + public Task InsertAndGetIdAsync(WebhookEvent webhookEvent) + { + return Task.FromResult(default); + } + + public Task GetAsync(Guid? tenantId, Guid id) + { + return Task.FromResult(default); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs new file mode 100644 index 000000000..7254b1ae6 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs @@ -0,0 +1,47 @@ +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 GetAsync(Guid? tenantId, Guid id) + { + return Task.FromResult(default); + } + + public Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId) + { + return Task.FromResult(int.MaxValue); + } + + public Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount) + { + return default; + } + + public Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, + int skipCount) + { + return Task.FromResult(ValueTuple.Create(0, new List() as IReadOnlyCollection)); + } + + public Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId) + { + return Task.FromResult(new List()); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs new file mode 100644 index 000000000..4214d1d25 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// Null pattern implementation of . + /// It's used if is not implemented by actual persistent store + /// + public class NullWebhookSubscriptionsStore : IWebhookSubscriptionsStore + { + public static NullWebhookSubscriptionsStore Instance { get; } = new NullWebhookSubscriptionsStore(); + + public Task GetAsync(Guid id) + { + return Task.FromResult(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> GetAllSubscriptionsAsync(Guid? tenantId) + { + return Task.FromResult(new List()); + } + + public Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName) + { + return Task.FromResult(new List()); + } + + public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) + { + return Task.FromResult(new List()); + } + + public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName) + { + return Task.FromResult(new List()); + } + + public Task IsSubscribedAsync(Guid? tenantId, string webhookName) + { + return Task.FromResult(false); + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs new file mode 100644 index 000000000..1c1461e8a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookDefinition + { + /// + /// Unique name of the webhook. + /// + public string Name { get; } + + /// + /// Tries to send webhook only one time without checking to send attempt count + /// + public bool TryOnce { get; set; } + + /// + /// Defined maximum number of sending times + /// + public int MaxSendAttemptCount { get; set; } + + /// + /// Display name of the webhook. + /// Optional. + /// + public ILocalizableString DisplayName { get; set; } + + /// + /// Description for the webhook. + /// Optional. + /// + public ILocalizableString Description { get; set; } + + public List 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(); + } + + public WebhookDefinition WithFeature(params string[] features) + { + if (!features.IsNullOrEmpty()) + { + RequiredFeatures.AddRange(features); + } + + return this; + } + + public override string ToString() + { + return $"[{nameof(WebhookDefinition)} {Name}]"; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs new file mode 100644 index 000000000..5e8987a8b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs @@ -0,0 +1,55 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookDefinitionContext : IWebhookDefinitionContext + { + protected Dictionary Groups { get; } + + public WebhookDefinitionContext(Dictionary 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); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs new file mode 100644 index 000000000..c0f96f20e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs @@ -0,0 +1,139 @@ +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 WebhookGroupDefinitions => _lazyWebhookGroupDefinitions.Value; + private readonly Lazy> _lazyWebhookGroupDefinitions; + + protected IDictionary WebhookDefinitions => _lazyWebhookDefinitions.Value; + private readonly Lazy> _lazyWebhookDefinitions; + + private readonly IServiceProvider _serviceProvider; + private readonly AbpWebhooksOptions _options; + + public WebhookDefinitionManager( + IServiceProvider serviceProvider, + IOptions options) + { + _serviceProvider = serviceProvider; + _options = options.Value; + + _lazyWebhookGroupDefinitions = new Lazy>(CreateWebhookGroupDefinitions); + _lazyWebhookDefinitions = new Lazy>(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 GetAll() + { + return WebhookDefinitions.Values.ToImmutableList(); + } + + public IReadOnlyList GetGroups() + { + return WebhookGroupDefinitions.Values.ToImmutableList(); + } + + public async Task 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(); + var featureChecker = _serviceProvider.GetRequiredService(); + using (currentTenant.Change(tenantId)) + { + if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray())) + { + return false; + } + } + + return true; + } + + protected virtual Dictionary CreateWebhookDefinitions() + { + var definitions = new Dictionary(); + + 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 CreateWebhookGroupDefinitions() + { + var definitions = new Dictionary(); + + 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; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs new file mode 100644 index 000000000..ead9ab679 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs @@ -0,0 +1,13 @@ +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Webhooks +{ + public abstract class WebhookDefinitionProvider : ITransientDependency + { + /// + /// Used to add/manipulate webhook definitions. + /// + /// Context, + public abstract void Define(IWebhookDefinitionContext context); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs new file mode 100644 index 000000000..bd32fa76d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs @@ -0,0 +1,30 @@ +using System; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// Store created web hooks. To see who get that webhook check with and you can get + /// + public class WebhookEvent + { + public Guid Id { get; set; } + + /// + /// Webhook unique name + /// + public string WebhookName { get; set; } + + /// + /// Webhook data as JSON string. + /// + 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; } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs new file mode 100644 index 000000000..ae58049ed --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs @@ -0,0 +1,101 @@ +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 Properties { get; } + + private ILocalizableString _displayName; + public ILocalizableString DisplayName + { + get { + return _displayName; + } + set { + _displayName = value; + } + } + + public IReadOnlyList Webhooks => _webhooks.ToImmutableList(); + private readonly List _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(); + _webhooks = new List(); + } + + 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}]"; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs new file mode 100644 index 000000000..91442552f --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookHeader + { + /// + /// If true, webhook will only contain given headers. If false given headers will be added to predefined headers in subscription. + /// Default is false + /// + public bool UseOnlyGivenHeaders { get; set; } + + /// + /// That headers will be sent with the webhook. + /// + public IDictionary Headers { get; set; } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookManager.cs new file mode 100644 index 000000000..854795786 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookManager.cs @@ -0,0 +1,80 @@ +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 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 GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs) + { + if (webhookSenderArgs.SendExactSameData) + { + return webhookSenderArgs.Data; + } + + var payload = await GetWebhookPayloadAsync(webhookSenderArgs); + + var serializedBody = JsonConvert.SerializeObject(payload); + + return serializedBody; + } + + public abstract Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); + + public abstract Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs new file mode 100644 index 000000000..ff287cbd0 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs @@ -0,0 +1,35 @@ +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; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs new file mode 100644 index 000000000..8189f8f72 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs @@ -0,0 +1,39 @@ +using System; +using System.Net; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// Table for store webhook work items. Each item stores web hook send attempt of to subscribed tenants + /// + public class WebhookSendAttempt + { + public Guid Id { get; set; } + + /// + /// foreign id + /// + public Guid WebhookEventId { get; set; } + + /// + /// foreign id + /// + public Guid WebhookSubscriptionId { get; set; } + + /// + /// Webhook response content that webhook endpoint send back + /// + public string Response { get; set; } + + /// + /// Webhook response status code that webhook endpoint send back + /// + public HttpStatusCode? ResponseStatusCode { get; set; } + + public DateTime CreationTime { get; set; } + + public DateTime? LastModificationTime { get; set; } + + public Guid? TenantId { get; set; } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs new file mode 100644 index 000000000..cb30acbf9 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookSenderArgs + { + public Guid? TenantId { get; set; } + + //Webhook information + + /// + /// foreign id + /// + public Guid WebhookEventId { get; set; } + + /// + /// Webhook unique name + /// + public string WebhookName { get; set; } + + /// + /// Webhook data as JSON string. + /// + public string Data { get; set; } + + //Subscription information + + /// + /// foreign id + /// + public Guid WebhookSubscriptionId { get; set; } + + /// + /// Subscription webhook endpoint + /// + public string WebhookUri { get; set; } + + /// + /// Webhook secret + /// + public string Secret { get; set; } + + /// + /// Gets a set of additional HTTP headers.That headers will be sent with the webhook. + /// + public IDictionary Headers { get; set; } + + /// + /// True: It sends the exact same data as the parameter to clients. + /// + /// False: It sends data in . It is recommended way. + /// + /// + public bool SendExactSameData { get; set; } + + public WebhookSenderArgs() + { + Headers = new Dictionary(); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs new file mode 100644 index 000000000..9e4e3bf2a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookSubscriptionInfo + { + public Guid Id { get; set; } + /// + /// Subscribed Tenant's id . + /// + public Guid? TenantId { get; set; } + + /// + /// Subscription webhook endpoint + /// + public string WebhookUri { get; set; } + + /// + /// Webhook secret + /// + public string Secret { get; set; } + + /// + /// Is subscription active + /// + public bool IsActive { get; set; } + + /// + /// Subscribed webhook definitions unique names.It contains webhook definitions list as json + /// + /// Do not change it manually. + /// Use , + /// , + /// and + /// to change it. + /// + /// + public List Webhooks { get; set; } + + /// + /// Gets a set of additional HTTP headers.That headers will be sent with the webhook. It contains webhook header dictionary as json + /// + /// Do not change it manually. + /// Use , + /// , + /// , + /// to change it. + /// + /// + public IDictionary Headers { get; set; } + + public WebhookSubscriptionInfo() + { + IsActive = true; + Headers = new Dictionary(); + Webhooks = new List(); + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs new file mode 100644 index 000000000..fe1a9c697 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs @@ -0,0 +1,172 @@ +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 GetAsync(Guid id) + { + return await WebhookSubscriptionsStore.GetAsync(id); + } + + public virtual async Task> GetAllSubscriptionsAsync(Guid? tenantId) + { + return await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId); + } + + public virtual async Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName) + { + if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) + { + return new List(); + } + + return (await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId, webhookName)).ToList(); + } + + public virtual async Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) + { + return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(tenantIds)).ToList(); + } + + public virtual async Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName) + { + var featureGrantedTenants = new List(); + 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 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 + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/System/AbpStringCryptographyExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/System/AbpStringCryptographyExtensions.cs new file mode 100644 index 000000000..f4202f6e2 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks/System/AbpStringCryptographyExtensions.cs @@ -0,0 +1,13 @@ +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)); + } +}