diff --git a/.github/workflows/4testing-multi-build.yml b/.github/workflows/4testing-multi-build.yml index 643463e2c9..cbf5a66259 100644 --- a/.github/workflows/4testing-multi-build.yml +++ b/.github/workflows/4testing-multi-build.yml @@ -25,13 +25,21 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build 4testing run: | + export BUILD_NUMBER="$(date "+%Y%m%d%H")" cd ./build/install/docker REPO="onlyoffice" \ DOCKER_IMAGE_PREFIX="4testing-docspace" \ - DOCKER_TAG="develop" \ + DOCKER_TAG=$GITHUB_REF_NAME \ DOCKERFILE="Dockerfile.app" \ docker buildx bake -f build.yml \ - --set *.args.GIT_BRANCH="develop" \ + --set *.args.GIT_BRANCH=$GITHUB_REF_NAME \ --set *.platform=linux/amd64 \ --push + REPO="onlyoffice" \ + DOCKER_IMAGE_PREFIX="4testing-docspace" \ + DOCKER_TAG=$GITHUB_REF_NAME-$BUILD_NUMBER \ + DOCKERFILE="Dockerfile.app" \ + docker buildx bake -f build.yml \ + --set *.args.GIT_BRANCH=$GITHUB_REF_NAME \ + --push shell: bash diff --git a/ASC.Web.sln b/ASC.Web.sln index 1d022c4d31..8f1922e593 100644 --- a/ASC.Web.sln +++ b/ASC.Web.sln @@ -71,8 +71,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Data.Backup", "common\s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Webhooks.Core", "common\ASC.Webhooks.Core\ASC.Webhooks.Core.csproj", "{760BFF3A-1A67-43A1-A94C-78D11A4BB1E6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Webhooks.Service", "common\services\ASC.Webhooks.Service\ASC.Webhooks.Service.csproj", "{DAE2912D-1465-4D60-B1D7-90EE835003E4}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Data.Backup.BackgroundTasks", "common\services\ASC.Data.Backup.BackgroundTasks\ASC.Data.Backup.BackgroundTasks.csproj", "{C0C28A02-943C-4A38-B474-A2B49C6201ED}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.EventBus", "common\ASC.EventBus\ASC.EventBus.csproj", "{26540DA7-604B-474B-97BA-9CDC85A84B6D}" @@ -219,10 +217,6 @@ Global {760BFF3A-1A67-43A1-A94C-78D11A4BB1E6}.Debug|Any CPU.Build.0 = Debug|Any CPU {760BFF3A-1A67-43A1-A94C-78D11A4BB1E6}.Release|Any CPU.ActiveCfg = Release|Any CPU {760BFF3A-1A67-43A1-A94C-78D11A4BB1E6}.Release|Any CPU.Build.0 = Release|Any CPU - {DAE2912D-1465-4D60-B1D7-90EE835003E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DAE2912D-1465-4D60-B1D7-90EE835003E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DAE2912D-1465-4D60-B1D7-90EE835003E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DAE2912D-1465-4D60-B1D7-90EE835003E4}.Release|Any CPU.Build.0 = Release|Any CPU {C0C28A02-943C-4A38-B474-A2B49C6201ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C0C28A02-943C-4A38-B474-A2B49C6201ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {C0C28A02-943C-4A38-B474-A2B49C6201ED}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/ASC.Web.slnf b/ASC.Web.slnf index 6b0cc07eac..2d96054968 100644 --- a/ASC.Web.slnf +++ b/ASC.Web.slnf @@ -20,6 +20,7 @@ "common\\ASC.MessagingSystem\\ASC.MessagingSystem.csproj", "common\\ASC.Notify.Textile\\ASC.Notify.Textile.csproj", "common\\ASC.Textile\\ASC.Textile.csproj", + "common\\ASC.Webhooks.Core\\ASC.Webhooks.Core.csproj", "common\\services\\ASC.ApiCache\\ASC.ApiCache.csproj", "common\\services\\ASC.ApiSystem\\ASC.ApiSystem.csproj", "common\\services\\ASC.AuditTrail\\ASC.AuditTrail.csproj", diff --git a/build/install/docker/.env b/build/install/docker/.env index 6ee164e98c..eecd69053e 100644 --- a/build/install/docker/.env +++ b/build/install/docker/.env @@ -43,7 +43,7 @@ DOCUMENT_SERVER_URL_INTERNAL=http://${DOCUMENT_SERVER_HOST}/ MYSQL_ROOT_PASSWORD=my-secret-pw - MYSQL_DATABASE=${PRODUCT} + MYSQL_DATABASE=docspace MYSQL_USER=${PRODUCT}_user MYSQL_PASSWORD=${PRODUCT}_pass MYSQL_HOST=${CONTAINER_PREFIX}mysql-server diff --git a/common/ASC.Api.Core/Core/BaseStartup.cs b/common/ASC.Api.Core/Core/BaseStartup.cs index 30df4a9537..5d721d0f29 100644 --- a/common/ASC.Api.Core/Core/BaseStartup.cs +++ b/common/ASC.Api.Core/Core/BaseStartup.cs @@ -45,6 +45,7 @@ public abstract class BaseStartup protected DIHelper DIHelper { get; } protected bool LoadProducts { get; set; } = true; protected bool LoadConsumers { get; } = true; + protected bool WebhooksEnabled { get; set; } public BaseStartup(IConfiguration configuration, IHostEnvironment hostEnvironment) { @@ -293,9 +294,9 @@ public abstract class BaseStartup app.UseLoggerMiddleware(); - app.UseEndpoints(endpoints => + app.UseEndpoints(async endpoints => { - endpoints.MapCustom(); + await endpoints.MapCustom(WebhooksEnabled, app.ApplicationServices); endpoints.MapHealthChecks("/health", new HealthCheckOptions() { diff --git a/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs b/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs index 71ff6246af..12c547d89b 100644 --- a/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs +++ b/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs @@ -76,13 +76,59 @@ public class CustomEndpointDataSource : EndpointDataSource public static class EndpointExtension { - public static IEndpointRouteBuilder MapCustom(this IEndpointRouteBuilder endpoints) + private static readonly IReadOnlyList _methodList = new List + { + "POST", + "PUT", + "DELETE" + }; + + public static async Task MapCustom(this IEndpointRouteBuilder endpoints, bool webhooksEnabled = false, IServiceProvider serviceProvider = null) { endpoints.MapControllers(); + + if (webhooksEnabled && serviceProvider != null) + { + await endpoints.RegisterWebhooks(serviceProvider); + } + var sources = endpoints.DataSources.First(); endpoints.DataSources.Clear(); endpoints.DataSources.Add(new CustomEndpointDataSource(sources)); return endpoints; } + + private static async Task RegisterWebhooks(this IEndpointRouteBuilder endpoints, IServiceProvider serviceProvider) + { + var toRegister = endpoints.DataSources.First().Endpoints + .Cast() + .SelectMany(r => + { + var result = new List(); + var httpMethodMetadata = r.Metadata.OfType().FirstOrDefault(); + var disabled = r.Metadata.OfType().FirstOrDefault(); + + if (disabled == null) + { + foreach (var httpMethod in httpMethodMetadata.HttpMethods) + { + result.Add(new Webhook { Method = httpMethod, Route = r.RoutePattern.RawText.ToLower() }); + } + } + return result; + }) + .Where(r => _methodList.Contains(r.Method)) + .DistinctBy(r => $"{r.Method}|{r.Route}") + .ToList(); + + using var scope = serviceProvider.CreateScope(); + var dbWorker = scope.ServiceProvider.GetService(); + if (dbWorker != null) + { + await dbWorker.Register(toRegister); + } + + return endpoints; + } } \ No newline at end of file diff --git a/common/ASC.Api.Core/Core/WebhookDisableAttribute.cs b/common/ASC.Api.Core/Core/WebhookDisableAttribute.cs new file mode 100644 index 0000000000..ef7a9c0654 --- /dev/null +++ b/common/ASC.Api.Core/Core/WebhookDisableAttribute.cs @@ -0,0 +1,32 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Api.Core.Core; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class WebhookDisableAttribute : Attribute +{ +} diff --git a/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs b/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs index 6b03a2b28d..1ef15ec0d3 100644 --- a/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs +++ b/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs @@ -33,31 +33,33 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable private Stream _bodyStream; private readonly IWebhookPublisher _webhookPublisher; private readonly ILogger _logger; - private static readonly List _methodList = new List { "POST", "UPDATE", "DELETE" }; + private readonly SettingsManager _settingsManager; + private readonly DbWorker _dbWorker; - public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher, ILogger logger) + public WebhooksGlobalFilterAttribute( + IWebhookPublisher webhookPublisher, + ILogger logger, + SettingsManager settingsManager, + DbWorker dbWorker) { _stream = new MemoryStream(); _webhookPublisher = webhookPublisher; _logger = logger; + _settingsManager = settingsManager; + _dbWorker = dbWorker; } - public override void OnResultExecuting(ResultExecutingContext context) + public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { - if (!Skip(context.HttpContext)) + if (!await Skip(context.HttpContext)) { _bodyStream = context.HttpContext.Response.Body; context.HttpContext.Response.Body = _stream; } - base.OnResultExecuting(context); - } - - public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) - { await base.OnResultExecutionAsync(context, next); - if (context.Cancel || Skip(context.HttpContext)) + if (context.Cancel || await Skip(context.HttpContext)) { return; } @@ -74,7 +76,9 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable var resultContent = Encoding.UTF8.GetString(_stream.ToArray()); - await _webhookPublisher.PublishAsync(method, routePattern, resultContent); + var webhook = await _dbWorker.GetWebhookAsync(method, routePattern); + + await _webhookPublisher.PublishAsync(webhook.Id, resultContent); } catch (Exception e) { @@ -100,16 +104,17 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable return (method, routePattern); } - private bool Skip(HttpContext context) + private async Task Skip(HttpContext context) { var (method, routePattern) = GetData(context); - if (!_methodList.Contains(method)) + if (routePattern == null) { return true; } - if (routePattern == null) + var webhook = await _dbWorker.GetWebhookAsync(method, routePattern); + if (webhook == null || _settingsManager.Load().Ids.Contains(webhook.Id)) { return true; } diff --git a/common/ASC.Data.Storage/S3/S3Storage.cs b/common/ASC.Data.Storage/S3/S3Storage.cs index 1b9e427d2e..24c7dd9285 100644 --- a/common/ASC.Data.Storage/S3/S3Storage.cs +++ b/common/ASC.Data.Storage/S3/S3Storage.cs @@ -56,6 +56,7 @@ public class S3Storage : BaseStorage private EncryptionMethod _encryptionMethod = EncryptionMethod.None; private string _encryptionKey; + private readonly IConfiguration _configuration; public S3Storage( TempStream tempStream, @@ -65,9 +66,11 @@ public class S3Storage : BaseStorage IHttpContextAccessor httpContextAccessor, ILoggerProvider factory, ILogger options, - IHttpClientFactory clientFactory) + IHttpClientFactory clientFactory, + IConfiguration configuration) : base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, factory, options, clientFactory) { + _configuration = configuration; } public Uri GetUriInternal(string path) @@ -1225,7 +1228,7 @@ public class S3Storage : BaseStorage var uploadId = initResponse.UploadId; - var partSize = 5 * (long)Math.Pow(2, 20); // Part size is 5 MB. + var partSize = GetChunkSize(); long bytePosition = 0; for (var i = 1; bytePosition < objectSize; i++) @@ -1463,7 +1466,19 @@ public class S3Storage : BaseStorage return el.ETag; } - + + private long GetChunkSize() + { + var configSetting = _configuration["files:uploader:chunk-size"]; + if (!string.IsNullOrEmpty(configSetting)) + { + configSetting = configSetting.Trim(); + return long.Parse(configSetting); + } + long defaultValue = 10 * 1024 * 1024; + return defaultValue; + } + private enum EncryptionMethod { None, diff --git a/common/ASC.Webhooks.Core/ASC.Webhooks.Core.csproj b/common/ASC.Webhooks.Core/ASC.Webhooks.Core.csproj index 5c3137faf4..4d63d521f3 100644 --- a/common/ASC.Webhooks.Core/ASC.Webhooks.Core.csproj +++ b/common/ASC.Webhooks.Core/ASC.Webhooks.Core.csproj @@ -37,4 +37,19 @@ + + + True + True + WebHookResource.resx + + + + + + ResXFileCodeGenerator + WebHookResource.Designer.cs + + + diff --git a/common/ASC.Webhooks.Core/DbWorker.cs b/common/ASC.Webhooks.Core/DbWorker.cs index 3704f51bfe..44d52d0bf4 100644 --- a/common/ASC.Webhooks.Core/DbWorker.cs +++ b/common/ASC.Webhooks.Core/DbWorker.cs @@ -24,6 +24,8 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +using AutoMapper; + namespace ASC.Webhooks.Core; [Scope] @@ -32,6 +34,7 @@ public class DbWorker private readonly IDbContextFactory _dbContextFactory; private readonly TenantManager _tenantManager; private readonly AuthContext _authContext; + private readonly IMapper _mapper; private int Tenant { @@ -41,18 +44,32 @@ public class DbWorker } } - public DbWorker(IDbContextFactory dbContextFactory, TenantManager tenantManager, AuthContext authContext) + public DbWorker( + IDbContextFactory dbContextFactory, + TenantManager tenantManager, + AuthContext authContext, + IMapper mapper) { _dbContextFactory = dbContextFactory; _tenantManager = tenantManager; _authContext = authContext; + _mapper = mapper; } - public async Task AddWebhookConfig(string name, string uri, string secretKey) + public async Task AddWebhookConfig(string uri, string secretKey) { - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - var toAdd = new WebhooksConfig { TenantId = Tenant, Uri = uri, SecretKey = secretKey, Name = name }; + var objForCreate = await webhooksDbContext.WebhooksConfigs + .Where(it => it.TenantId == Tenant && it.Uri == uri) + .FirstOrDefaultAsync(); + + if (objForCreate != null) + { + return objForCreate; + } + + var toAdd = new WebhooksConfig { TenantId = Tenant, Uri = uri, SecretKey = secretKey }; toAdd = await webhooksDbContext.AddOrUpdateAsync(r => r.WebhooksConfigs, toAdd); await webhooksDbContext.SaveChangesAsync(); @@ -83,7 +100,7 @@ public class DbWorker .AsAsyncEnumerable(); } - public async Task UpdateWebhookConfig(int id, string name, string uri, string key, bool? enabled) + public async Task UpdateWebhookConfig(int id, string uri, string key, bool? enabled) { using var webhooksDbContext = _dbContextFactory.CreateDbContext(); @@ -98,11 +115,6 @@ public class DbWorker updateObj.Uri = uri; } - if (!string.IsNullOrEmpty(name)) - { - updateObj.Name = name; - } - if (!string.IsNullOrEmpty(key)) { updateObj.SecretKey = key; @@ -128,15 +140,18 @@ public class DbWorker var removeObj = await webhooksDbContext.WebhooksConfigs .Where(it => it.TenantId == tenant && it.Id == id) - .FirstOrDefaultAsync(); - - webhooksDbContext.WebhooksConfigs.Remove(removeObj); - await webhooksDbContext.SaveChangesAsync(); + .FirstOrDefaultAsync(); + + if (removeObj != null) + { + webhooksDbContext.WebhooksConfigs.Remove(removeObj); + await webhooksDbContext.SaveChangesAsync(); + } return removeObj; } - public IAsyncEnumerable ReadJournal(int startIndex, int limit, DateTime? delivery, string hookname, string route) + public IAsyncEnumerable ReadJournal(int startIndex, int limit, DateTime? delivery, string hookUri, int hookId) { var webhooksDbContext = _dbContextFactory.CreateDbContext(); @@ -150,14 +165,14 @@ public class DbWorker q = q.Where(r => r.Delivery == date); } - if (!string.IsNullOrEmpty(hookname)) + if (!string.IsNullOrEmpty(hookUri)) { - q = q.Where(r => r.Config.Name == hookname); + q = q.Where(r => r.Config.Uri == hookUri); } - if (!string.IsNullOrEmpty(route)) + if (hookId != 0) { - q = q.Where(r => r.Route == route); + q = q.Where(r => r.WebhookId == hookId); } if (startIndex != 0) @@ -211,5 +226,54 @@ public class DbWorker await webhooksDbContext.SaveChangesAsync(); return webhook; + } + + public async Task Register(List webhooks) + { + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + var dbWebhooks = await webhooksDbContext.Webhooks.ToListAsync(); + + foreach (var webhook in webhooks) + { + if (!dbWebhooks.Any(r => r.Route == webhook.Route && r.Method == webhook.Method)) + { + try + { + await webhooksDbContext.Webhooks.AddAsync(_mapper.Map(webhook)); + await webhooksDbContext.SaveChangesAsync(); + } + catch (Exception) + { + + } + } + } + } + + public async Task> GetWebhooksAsync() + { + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + var webHooks = await webhooksDbContext.Webhooks.AsNoTracking().ToListAsync(); + return _mapper.Map, List>(webHooks); + } + + public async Task GetWebhookAsync(int id) + { + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + var webHook = await webhooksDbContext.Webhooks.Where(r => r.Id == id).AsNoTracking().FirstOrDefaultAsync(); + return _mapper.Map(webHook); + } + + public async Task GetWebhookAsync(string method, string routePattern) + { + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + var webHook = await webhooksDbContext.Webhooks + .Where(r => r.Method == method && r.Route == routePattern) + .AsNoTracking() + .FirstOrDefaultAsync(); + + return _mapper.Map(webHook); } } \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/EF/Context/WebhooksDbContext.cs b/common/ASC.Webhooks.Core/EF/Context/WebhooksDbContext.cs index 661ece604b..97aebf32aa 100644 --- a/common/ASC.Webhooks.Core/EF/Context/WebhooksDbContext.cs +++ b/common/ASC.Webhooks.Core/EF/Context/WebhooksDbContext.cs @@ -29,14 +29,16 @@ namespace ASC.Webhooks.Core.EF.Context; public class WebhooksDbContext : DbContext { public DbSet WebhooksConfigs { get; set; } - public DbSet WebhooksLogs { get; set; } + public DbSet WebhooksLogs { get; set; } + public DbSet Webhooks { get; set; } public WebhooksDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { ModelBuilderWrapper - .From(modelBuilder, Database) + .From(modelBuilder, Database) + .AddDbWebhooks() .AddWebhooksConfig() .AddWebhooksLog(); } diff --git a/common/ASC.Webhooks.Core/EF/Model/DbWebhook.cs b/common/ASC.Webhooks.Core/EF/Model/DbWebhook.cs new file mode 100644 index 0000000000..8875e143fa --- /dev/null +++ b/common/ASC.Webhooks.Core/EF/Model/DbWebhook.cs @@ -0,0 +1,95 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Webhooks.Core.EF.Model; + +public class DbWebhook : IMapFrom +{ + public int Id { get; set; } + public string Route { get; set; } + public string Method { get; set; } +} + +public static class DbWebhookExtension +{ + public static ModelBuilderWrapper AddDbWebhooks(this ModelBuilderWrapper modelBuilder) + { + return modelBuilder + .Add(MySqlAddDbWebhook, Provider.MySql) + .Add(PgSqlAddDbWebhook, Provider.PostgreSql); + } + + private static void MySqlAddDbWebhook(this ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.Id }) + .HasName("PRIMARY"); + + entity.ToTable("webhooks") + .HasCharSet("utf8"); + + entity.Property(e => e.Id) + .HasColumnType("int") + .HasColumnName("id"); + + entity.Property(e => e.Route) + .HasMaxLength(200) + .HasColumnName("route") + .HasDefaultValueSql("''"); + + entity.Property(e => e.Method) + .HasMaxLength(10) + .HasColumnName("method") + .HasDefaultValueSql("''"); + }); + } + + private static void PgSqlAddDbWebhook(this ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.Id }) + .HasName("PRIMARY"); + + entity.ToTable("webhooks"); + + entity.Property(e => e.Id) + .HasColumnType("int") + .HasColumnName("id"); + + entity.Property(e => e.Route) + .HasMaxLength(200) + .HasColumnName("route") + .HasDefaultValueSql("''"); + + entity.Property(e => e.Method) + .HasMaxLength(10) + .HasColumnName("method") + .HasDefaultValueSql("''"); + }); + } +} \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/EF/Model/WebhooksConfig.cs b/common/ASC.Webhooks.Core/EF/Model/WebhooksConfig.cs index 2232cb3bf9..e87eb11731 100644 --- a/common/ASC.Webhooks.Core/EF/Model/WebhooksConfig.cs +++ b/common/ASC.Webhooks.Core/EF/Model/WebhooksConfig.cs @@ -28,8 +28,7 @@ namespace ASC.Webhooks.Core.EF.Model; public class WebhooksConfig : BaseEntity { - public int Id { get; set; } - public string Name { get; set; } + public int Id { get; set; } public string SecretKey { get; set; } public int TenantId { get; set; } public string Uri { get; set; } @@ -80,11 +79,6 @@ public static class WebhooksConfigExtension .HasMaxLength(50) .HasColumnName("secret_key") .HasDefaultValueSql("''"); - - entity.Property(e => e.Name) - .HasMaxLength(50) - .HasColumnName("name") - .IsRequired(); entity.Property(e => e.Enabled) .HasColumnName("enabled") @@ -122,11 +116,6 @@ public static class WebhooksConfigExtension .HasMaxLength(50) .HasColumnName("secret_key") .HasDefaultValueSql("''"); - - entity.Property(e => e.Name) - .HasMaxLength(50) - .HasColumnName("name") - .IsRequired(); entity.Property(e => e.Enabled) .HasColumnName("enabled") diff --git a/common/ASC.Webhooks.Core/EF/Model/WebhooksLog.cs b/common/ASC.Webhooks.Core/EF/Model/WebhooksLog.cs index 3daa62b4c1..310edc5d57 100644 --- a/common/ASC.Webhooks.Core/EF/Model/WebhooksLog.cs +++ b/common/ASC.Webhooks.Core/EF/Model/WebhooksLog.cs @@ -27,12 +27,11 @@ namespace ASC.Webhooks.Core.EF.Model; public class WebhooksLog -{ +{ + public int Id { get; set; } public int ConfigId { get; set; } - public DateTime CreationTime { get; set; } - public int Id { get; set; } - public string Method { get; set; } - public string Route { get; set; } + public DateTime CreationTime { get; set; } + public int WebhookId { get; set; } public string RequestHeaders { get; set; } public string RequestPayload { get; set; } public string ResponseHeaders { get; set; } @@ -109,17 +108,12 @@ public static class WebhooksPayloadExtension entity.Property(e => e.ResponseHeaders) .HasColumnName("response_headers") - .HasColumnType("json"); - - entity.Property(e => e.Method) - .HasColumnType("varchar") - .HasColumnName("method") - .HasMaxLength(100); - - entity.Property(e => e.Route) - .HasColumnType("varchar") - .HasColumnName("route") - .HasMaxLength(100); + .HasColumnType("json"); + + entity.Property(e => e.WebhookId) + .HasColumnType("int") + .HasColumnName("webhook_id") + .IsRequired(); entity.Property(e => e.CreationTime) .HasColumnType("datetime") @@ -180,15 +174,10 @@ public static class WebhooksPayloadExtension .HasColumnName("response_headers") .HasColumnType("json"); - entity.Property(e => e.Method) - .HasColumnType("varchar") - .HasColumnName("method") - .HasMaxLength(100); - - entity.Property(e => e.Route) - .HasColumnType("varchar") - .HasColumnName("route") - .HasMaxLength(100); + entity.Property(e => e.WebhookId) + .HasColumnType("int") + .HasColumnName("webhook_id") + .IsRequired(); entity.Property(e => e.CreationTime) .HasColumnType("datetime") diff --git a/common/ASC.Webhooks.Core/GlobalUsings.cs b/common/ASC.Webhooks.Core/GlobalUsings.cs index 77edd6548b..3217b23001 100644 --- a/common/ASC.Webhooks.Core/GlobalUsings.cs +++ b/common/ASC.Webhooks.Core/GlobalUsings.cs @@ -24,13 +24,18 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +global using System.Text.Json.Serialization; + global using ASC.Common; global using ASC.Common.Caching; +global using ASC.Common.Mapping; global using ASC.Core; global using ASC.Core.Common.EF; global using ASC.Core.Common.EF.Model; +global using ASC.Core.Common.Settings; global using ASC.Web.Webhooks; global using ASC.Webhooks.Core.EF.Context; global using ASC.Webhooks.Core.EF.Model; - -global using Microsoft.EntityFrameworkCore; +global using ASC.Webhooks.Core.Resources; + +global using Microsoft.EntityFrameworkCore; \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/IWebhookPublisher.cs b/common/ASC.Webhooks.Core/IWebhookPublisher.cs index be00d4bd37..3352fcb407 100644 --- a/common/ASC.Webhooks.Core/IWebhookPublisher.cs +++ b/common/ASC.Webhooks.Core/IWebhookPublisher.cs @@ -29,6 +29,6 @@ namespace ASC.Webhooks.Core; [Scope] public interface IWebhookPublisher { - public Task PublishAsync(string method, string route, string requestPayload); - public Task PublishAsync(string method, string route, string requestPayload, int configId); + public Task PublishAsync(int webhookId, string requestPayload); + public Task PublishAsync(int webhookId, string requestPayload, int configId); } \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/Resources/WebHookResource.Designer.cs b/common/ASC.Webhooks.Core/Resources/WebHookResource.Designer.cs new file mode 100644 index 0000000000..7b479ee4d0 --- /dev/null +++ b/common/ASC.Webhooks.Core/Resources/WebHookResource.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ASC.Webhooks.Core.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class WebHookResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal WebHookResource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ASC.Webhooks.Core.Resources.WebHookResource", typeof(WebHookResource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Start Edit. + /// + internal static string POST_api_2_0_files_file__fileId__startedit { + get { + return ResourceManager.GetString("POST|api/2.0/files/file/{fileId}/startedit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Start Edit Description. + /// + internal static string POST_api_2_0_files_file__fileId__startedit_Description { + get { + return ResourceManager.GetString("POST|api/2.0/files/file/{fileId}/startedit_Description", resourceCulture); + } + } + } +} diff --git a/common/ASC.Webhooks.Core/Resources/WebHookResource.resx b/common/ASC.Webhooks.Core/Resources/WebHookResource.resx new file mode 100644 index 0000000000..1ffe801dd3 --- /dev/null +++ b/common/ASC.Webhooks.Core/Resources/WebHookResource.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Start Edit + + + Start Edit Description + + \ No newline at end of file diff --git a/common/services/ASC.Webhooks.Service/GlobalUsings.cs b/common/ASC.Webhooks.Core/WebHooksSettings.cs similarity index 65% rename from common/services/ASC.Webhooks.Service/GlobalUsings.cs rename to common/ASC.Webhooks.Core/WebHooksSettings.cs index 7099808a50..354c5bcf68 100644 --- a/common/services/ASC.Webhooks.Service/GlobalUsings.cs +++ b/common/ASC.Webhooks.Core/WebHooksSettings.cs @@ -1,51 +1,46 @@ -// (c) Copyright Ascensio System SIA 2010-2022 -// -// This program is a free software product. -// You can redistribute it and/or modify it under the terms -// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software -// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended -// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of -// any third-party rights. -// -// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see -// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html -// -// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. -// -// The interactive user interfaces in modified source and object code versions of the Program must -// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. -// -// Pursuant to Section 7(b) of the License you must retain the original Product logo when -// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under -// trademark law for use of our trademarks. -// -// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing -// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 -// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + + +namespace ASC.Webhooks.Core; + +[Serializable] +public class WebHooksSettings : ISettings +{ + public bool EnableSSLVerification { get; set; } + public List Ids { get; set; } + + [JsonIgnore] + public Guid ID => new Guid("6EFA0EAB-D033-4720-BDB3-DEB057EBC140"); -global using System.Collections.Concurrent; -global using System.Security.Cryptography; -global using System.Text; -global using System.Text.Json; - -global using ASC.Api.Core; -global using ASC.Api.Core.Extensions; -global using ASC.Common; -global using ASC.Common.Caching; -global using ASC.Common.Log; -global using ASC.Common.Utils; -global using ASC.Web.Webhooks; -global using ASC.Webhooks.Core; -global using ASC.Webhooks.Service; -global using ASC.Webhooks.Service.Log; -global using ASC.Webhooks.Service.Services; - -global using Microsoft.AspNetCore.Builder; -global using Microsoft.Extensions.Hosting.WindowsServices; -global using Microsoft.Extensions.Logging; - -global using Polly; -global using Polly.Extensions.Http; -global using ASC.Webhooks; -global using ASC.Webhooks.Extension; \ No newline at end of file + public WebHooksSettings GetDefault() => new WebHooksSettings() + { + EnableSSLVerification = true, + Ids = new List { } + }; +} + + diff --git a/common/ASC.Webhooks.Core/WebhookManager.cs b/common/ASC.Webhooks.Core/WebhookManager.cs new file mode 100644 index 0000000000..417af1aa15 --- /dev/null +++ b/common/ASC.Webhooks.Core/WebhookManager.cs @@ -0,0 +1,39 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Webhooks.Core; + +public class Webhook : IMapFrom +{ + public int Id { get; set; } + public string Route { get; set; } + public string Method { get; set; } + public bool Disable { get; set; } + public string Name { get => WebHookResource.ResourceManager.GetString(Endpoint) ?? ""; } + public string Description { get => WebHookResource.ResourceManager.GetString($"{Endpoint}_Description") ?? ""; } + + private string Endpoint { get => $"{Method}|{Route}"; } +} \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/WebhookPublisher.cs b/common/ASC.Webhooks.Core/WebhookPublisher.cs index f1d459b825..09b4130a93 100644 --- a/common/ASC.Webhooks.Core/WebhookPublisher.cs +++ b/common/ASC.Webhooks.Core/WebhookPublisher.cs @@ -40,7 +40,7 @@ public class WebhookPublisher : IWebhookPublisher _webhookNotify = webhookNotify; } - public async Task PublishAsync(string method, string route, string requestPayload) + public async Task PublishAsync(int webhookId, string requestPayload) { if (string.IsNullOrEmpty(requestPayload)) { @@ -51,11 +51,11 @@ public class WebhookPublisher : IWebhookPublisher await foreach (var config in webhookConfigs.Where(r => r.Enabled)) { - _ = await PublishAsync(method, route, requestPayload, config.Id); + _ = await PublishAsync(webhookId, requestPayload, config.Id); } } - public async Task PublishAsync(string method, string route, string requestPayload, int configId) + public async Task PublishAsync(int webhookId, string requestPayload, int configId) { if (string.IsNullOrEmpty(requestPayload)) { @@ -64,8 +64,7 @@ public class WebhookPublisher : IWebhookPublisher var webhooksLog = new WebhooksLog { - Method = method, - Route = route, + WebhookId = webhookId, CreationTime = DateTime.UtcNow, RequestPayload = requestPayload, ConfigId = configId diff --git a/common/Tools/ASC.Migration.Creator/appsettings.creator.json b/common/Tools/ASC.Migration.Creator/appsettings.creator.json index b1f7a94299..78206a259d 100644 --- a/common/Tools/ASC.Migration.Creator/appsettings.creator.json +++ b/common/Tools/ASC.Migration.Creator/appsettings.creator.json @@ -5,19 +5,19 @@ { "Provider": "MySql", "ProviderFullName": "MySql.Data.MySqlClient", - "ConnectionString": "Server=localhost;Database=onlyoffice;User ID=root;Password=root" + "ConnectionString": "Server=localhost;Database=docspace;User ID=root;Password=root" }, { "Provider": "PostgreSql", "ProviderFullName": "Npgsql", - "ConnectionString": "Host=localhost;Port=5432;Database=onlyoffice;Username=postgres;Password=dev;" + "ConnectionString": "Host=localhost;Port=5432;Database=docspace;Username=postgres;Password=dev;" } ], "TeamlabsiteProviders": [ { "Provider": "MySql", "ProviderFullName": "MySql.Data.MySqlClient", - "ConnectionString": "Server=localhost;Database=teamlabsite;User ID=root;Password=root" + "ConnectionString": "Server=localhost;Database=docspacesite;User ID=root;Password=root" } ] } diff --git a/common/Tools/ASC.Migration.Runner/appsettings.runner.json b/common/Tools/ASC.Migration.Runner/appsettings.runner.json index 8f8f45762f..ebd57f4ab9 100644 --- a/common/Tools/ASC.Migration.Runner/appsettings.runner.json +++ b/common/Tools/ASC.Migration.Runner/appsettings.runner.json @@ -4,7 +4,7 @@ { "Provider": "MySql", "ProviderFullName": "MySql.Data.MySqlClient", - "ConnectionString": "Server=localhost;Database=onlyoffice;User ID=root;Password=root" + "ConnectionString": "Server=localhost;Database=docspace;User ID=root;Password=root" } ], "TeamlabsiteProviders": [ diff --git a/common/services/ASC.Studio.Notify/GlobalUsings.cs b/common/services/ASC.Studio.Notify/GlobalUsings.cs index 0f1a8a244b..d1452ada7c 100644 --- a/common/services/ASC.Studio.Notify/GlobalUsings.cs +++ b/common/services/ASC.Studio.Notify/GlobalUsings.cs @@ -30,9 +30,13 @@ global using ASC.Common; global using ASC.Common.DependencyInjection; global using ASC.Core.Notify; global using ASC.Notify; +global using ASC.Notify.Extension; +global using ASC.Studio.Notify; global using ASC.Web.Studio.Core.Notify; global using Autofac; global using Microsoft.AspNetCore.Builder; global using Microsoft.Extensions.Hosting.WindowsServices; + +global using NLog; \ No newline at end of file diff --git a/common/services/ASC.Studio.Notify/Program.cs b/common/services/ASC.Studio.Notify/Program.cs index 7a828dec73..5415b0ec4e 100644 --- a/common/services/ASC.Studio.Notify/Program.cs +++ b/common/services/ASC.Studio.Notify/Program.cs @@ -24,11 +24,6 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode -using ASC.Notify.Extension; -using ASC.Studio.Notify; - -using NLog; - var options = new WebApplicationOptions { Args = args, @@ -42,13 +37,14 @@ builder.Configuration.AddDefaultConfiguration(builder.Environment) .AddEnvironmentVariables() .AddCommandLine(args); -var logger = LogManager.Setup() - .SetupExtensions(s => - { - s.RegisterLayoutRenderer("application-context", (logevent) => AppName); - }) - .LoadConfiguration(builder.Configuration, builder.Environment) - .GetLogger(typeof(Startup).Namespace); +var logger = LogManager + .Setup() + .SetupExtensions(s => + { + s.RegisterLayoutRenderer("application-context", (logevent) => AppName); + }) + .LoadConfiguration(builder.Configuration, builder.Environment) + .GetLogger(typeof(Startup).Namespace); try { diff --git a/common/services/ASC.Webhooks.Service/ASC.Webhooks.Service.csproj b/common/services/ASC.Webhooks.Service/ASC.Webhooks.Service.csproj deleted file mode 100644 index c8d7efc9a9..0000000000 --- a/common/services/ASC.Webhooks.Service/ASC.Webhooks.Service.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net7.0 - false - enable - - - - - - - - - - - diff --git a/common/services/ASC.Webhooks.Service/Program.cs b/common/services/ASC.Webhooks.Service/Program.cs deleted file mode 100644 index 8d4b908bcb..0000000000 --- a/common/services/ASC.Webhooks.Service/Program.cs +++ /dev/null @@ -1,87 +0,0 @@ -// (c) Copyright Ascensio System SIA 2010-2022 -// -// This program is a free software product. -// You can redistribute it and/or modify it under the terms -// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software -// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended -// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of -// any third-party rights. -// -// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see -// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html -// -// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. -// -// The interactive user interfaces in modified source and object code versions of the Program must -// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. -// -// Pursuant to Section 7(b) of the License you must retain the original Product logo when -// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under -// trademark law for use of our trademarks. -// -// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing -// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 -// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode - -using NLog; - -var options = new WebApplicationOptions -{ - Args = args, - ContentRootPath = WindowsServiceHelpers.IsWindowsService() ? AppContext.BaseDirectory : default -}; - -var builder = WebApplication.CreateBuilder(options); - -builder.Configuration.AddDefaultConfiguration(builder.Environment) - .AddWebhookConfiguration() - .AddEnvironmentVariables() - .AddCommandLine(args); - -var logger = LogManager.Setup() - .SetupExtensions(s => - { - s.RegisterLayoutRenderer("application-context", (logevent) => AppName); - }) - .LoadConfiguration(builder.Configuration, builder.Environment) - .GetLogger(typeof(Startup).Namespace); - -try -{ - logger.Info("Configuring web host ({applicationContext})...", AppName); - builder.Host.ConfigureDefault(); - builder.WebHost.ConfigureDefaultKestrel(); - - var startup = new Startup(builder.Configuration, builder.Environment); - - startup.ConfigureServices(builder.Services); - - var app = builder.Build(); - - startup.Configure(app); - - logger.Info("Starting web host ({applicationContext})...", AppName); - await app.RunWithTasksAsync(); -} -catch (Exception ex) -{ - if (logger != null) - { - logger.Error(ex, "Program terminated unexpectedly ({applicationContext})!", AppName); - } - - throw; -} -finally -{ - // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) - LogManager.Shutdown(); -} - -public partial class Program -{ - public static string Namespace = typeof(Startup).Namespace; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.') + 1).Replace(".", ""); -} - diff --git a/common/services/ASC.Webhooks.Service/Properties/launchSettings.json b/common/services/ASC.Webhooks.Service/Properties/launchSettings.json deleted file mode 100644 index f575fdec41..0000000000 --- a/common/services/ASC.Webhooks.Service/Properties/launchSettings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "profiles": { - "Kestrel WebServer": { - "commandName": "Project", - "launchBrowser": false, - "environmentVariables": { - "$STORAGE_ROOT": "../../../Data", - "log__name": "webhooks", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products", - "ASPNETCORE_URLS": "http://localhost:5031", - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "WSL 2 : Ubuntu 20.04": { - "commandName": "WSL2", - "launchBrowser": false, - "environmentVariables": { - "$STORAGE_ROOT": "../../../Data", - "log__name": "webhooks", - "log__dir": "../../../Logs", - "core__products__folder": "../../../products", - "ASPNETCORE_URLS": "http://localhost:5031", - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "distributionName": "Ubuntu-20.04" - } - } -} \ No newline at end of file diff --git a/common/services/ASC.Webhooks.Service/appsettings.json b/common/services/ASC.Webhooks.Service/appsettings.json deleted file mode 100644 index 9bf702e310..0000000000 --- a/common/services/ASC.Webhooks.Service/appsettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "pathToConf": "..\\..\\..\\config" -} diff --git a/config/apisystem.dev.json b/config/apisystem.dev.json index b064345659..82331c07d4 100644 --- a/config/apisystem.dev.json +++ b/config/apisystem.dev.json @@ -39,7 +39,7 @@ "ConnectionStrings": { "default": { "name": "default", - "connectionString": "Server=onlyoffice-mysql-server;Port=3306;Database=onlyoffice;User ID=onlyoffice_user;Password=onlyoffice_pass;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;ConnectionReset=false", + "connectionString": "Server=onlyoffice-mysql-server;Port=3306;Database=docspace;User ID=onlyoffice_user;Password=onlyoffice_pass;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;ConnectionReset=false", "providerName": "MySql.Data.MySqlClient" } }, diff --git a/config/apisystem.json b/config/apisystem.json index a7ad52cb9f..0a424d472e 100644 --- a/config/apisystem.json +++ b/config/apisystem.json @@ -39,7 +39,7 @@ "ConnectionStrings": { "default": { "name": "default", - "connectionString": "Server=localhost;Database=onlyoffice;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;ConnectionReset=false", + "connectionString": "Server=localhost;Database=docspace;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;ConnectionReset=false", "providerName": "MySql.Data.MySqlClient" } }, diff --git a/config/appsettings.json b/config/appsettings.json index f565831710..7b027f8425 100644 --- a/config/appsettings.json +++ b/config/appsettings.json @@ -79,8 +79,8 @@ "reviewed-docs": [ ".docx", ".docxf" ], "viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".epub", ".pdf", ".djvu", ".xps" ], "secret": { - "value": "", - "header": "" + "value": "secret", + "header": "AuthorizationJwt" }, "url": { "public": "http://localhost:8085/", @@ -93,7 +93,7 @@ "exts": [ "avi", "mpeg", "mpg", "wmv" ] }, "uploader": { - "chunk-size": 10485760, + "chunk-size": 20971520, "url": "/" }, "viewed-images": [ ".svg", ".bmp", ".gif", ".jpeg", ".jpg", ".png", ".ico", ".tif", ".tiff", ".webp" ], @@ -128,22 +128,22 @@ "ConnectionStrings": { "default": { "name": "default", - "connectionString": "Server=localhost;Database=onlyoffice;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;Connection Timeout=30;Maximum Pool Size=300;ConnectionReset=false", + "connectionString": "Server=localhost;Database=docspace;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;Connection Timeout=30;Maximum Pool Size=300;ConnectionReset=false", "providerName": "MySql.Data.MySqlClient" }, "postgre": { "name": "postgre", - "connectionString": "Host=localhost;Port=5432;Database=onlyoffice;Username=postgres;Password=dev;", + "connectionString": "Host=localhost;Port=5432;Database=docspace;Username=postgres;Password=dev;", "providerName": "Npgsql" }, "mysql": { "name": "mysql", - "connectionString": "Server=localhost;Database=onlyoffice;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;Connection Timeout=30;Maximum Pool Size=300;ConnectionReset=false", + "connectionString": "Server=localhost;Database=docspace;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;Connection Timeout=30;Maximum Pool Size=300;ConnectionReset=false", "providerName": "MySql.Data.MySqlClient" }, "teamlabsite": { - "name": "teamlabsite", - "connectionString": "Server=localhost;Database=teamlabsite;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;Connection Timeout=30;Maximum Pool Size=300;ConnectionReset=false", + "name": "docspacesite", + "connectionString": "Server=localhost;Database=docspacesite;User ID=dev;Password=dev;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;AllowPublicKeyRetrieval=True;Connection Timeout=30;Maximum Pool Size=300;ConnectionReset=false", "providerName": "MySql.Data.MySqlClient" } }, diff --git a/config/appsettings.test.json b/config/appsettings.test.json index c41dd9ed24..3a4d202465 100644 --- a/config/appsettings.test.json +++ b/config/appsettings.test.json @@ -9,7 +9,7 @@ "ConnectionStrings": { "default": { "name": "default", - "connectionString": "Server=172.18.0.2;Port=3306;Database=onlyoffice;User ID=onlyoffice_user;Password=onlyoffice_pass;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;ConnectionReset=false", + "connectionString": "Server=172.18.0.2;Port=3306;Database=docspace;User ID=onlyoffice_user;Password=onlyoffice_pass;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none;ConnectionReset=false", "providerName": "MySql.Data.MySqlClient" } }, diff --git a/migrations/mysql/WebhooksDbContext/20221019144343_WebhooksDbContextMigrate.Designer.cs b/migrations/mysql/WebhooksDbContext/20221019144343_WebhooksDbContextMigrate.Designer.cs index f4b7e478f3..568c54fea2 100644 --- a/migrations/mysql/WebhooksDbContext/20221019144343_WebhooksDbContextMigrate.Designer.cs +++ b/migrations/mysql/WebhooksDbContext/20221019144343_WebhooksDbContextMigrate.Designer.cs @@ -19,7 +19,34 @@ namespace ASC.Migrations.MySql.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("ProductVersion", "6.0.7") - .HasAnnotation("Relational:MaxIdentifierLength", 64); + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Route") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnName("route"); + + b.Property("Method") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("method"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("webhooks", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b => { @@ -88,11 +115,6 @@ namespace ASC.Migrations.MySql.Migrations .HasColumnType("datetime") .HasColumnName("delivery"); - b.Property("Method") - .HasMaxLength(100) - .HasColumnType("varchar(100)") - .HasColumnName("method"); - b.Property("RequestHeaders") .HasColumnType("json") .HasColumnName("request_headers"); @@ -114,10 +136,9 @@ namespace ASC.Migrations.MySql.Migrations .UseCollation("utf8_general_ci") .HasAnnotation("MySql:CharSet", "utf8"); - b.Property("Route") - .HasMaxLength(100) - .HasColumnType("varchar(100)") - .HasColumnName("route"); + b.Property("ConfigId") + .HasColumnType("int") + .HasColumnName("config_id"); b.Property("Status") .HasColumnType("int") diff --git a/migrations/mysql/WebhooksDbContext/20221019144343_WebhooksDbContextMigrate.cs b/migrations/mysql/WebhooksDbContext/20221019144343_WebhooksDbContextMigrate.cs index f173d6e99b..d3a9bc07dd 100644 --- a/migrations/mysql/WebhooksDbContext/20221019144343_WebhooksDbContextMigrate.cs +++ b/migrations/mysql/WebhooksDbContext/20221019144343_WebhooksDbContextMigrate.cs @@ -1,100 +1,141 @@ -using System; +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace ASC.Migrations.MySql.Migrations +namespace ASC.Migrations.MySql.Migrations; + +public partial class WebhooksDbContextMigrate : Migration { - public partial class WebhooksDbContextMigrate : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("MySql:CharSet", "utf8mb4"); + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "webhooks", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + route = table.Column(type: "varchar(200)", maxLength: 200, nullable: false, defaultValueSql: "''") + .Annotation("MySql:CharSet", "utf8"), + method = table.Column(type: "varchar(10)", maxLength: 10, nullable: false, defaultValueSql: "''") + .Annotation("MySql:CharSet", "utf8") + }, + constraints: table => + { + table.PrimaryKey("PRIMARY", x => x.id); + }) + .Annotation("MySql:CharSet", "utf8"); - migrationBuilder.CreateTable( - name: "webhooks_config", - columns: table => new - { - id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - name = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) - .Annotation("MySql:CharSet", "utf8"), - secret_key = table.Column(type: "varchar(50)", maxLength: 50, nullable: true, defaultValueSql: "''") - .Annotation("MySql:CharSet", "utf8"), - tenant_id = table.Column(type: "int unsigned", nullable: false), - uri = table.Column(type: "varchar(50)", maxLength: 50, nullable: true, defaultValueSql: "''") - .Annotation("MySql:CharSet", "utf8"), - enabled = table.Column(type: "tinyint(1)", nullable: false, defaultValueSql: "'1'") - }, - constraints: table => - { - table.PrimaryKey("PRIMARY", x => x.id); - }) - .Annotation("MySql:CharSet", "utf8"); + migrationBuilder.CreateTable( + name: "webhooks_config", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + name = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) + .Annotation("MySql:CharSet", "utf8"), + secret_key = table.Column(type: "varchar(50)", maxLength: 50, nullable: true, defaultValueSql: "''") + .Annotation("MySql:CharSet", "utf8"), + tenant_id = table.Column(type: "int unsigned", nullable: false), + uri = table.Column(type: "varchar(50)", maxLength: 50, nullable: true, defaultValueSql: "''") + .Annotation("MySql:CharSet", "utf8"), + enabled = table.Column(type: "tinyint(1)", nullable: false, defaultValueSql: "'1'") + }, + constraints: table => + { + table.PrimaryKey("PRIMARY", x => x.id); + }) + .Annotation("MySql:CharSet", "utf8"); - migrationBuilder.CreateTable( - name: "webhooks_logs", - columns: table => new - { - id = table.Column(type: "int", nullable: false) - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - config_id = table.Column(type: "int", nullable: false), - creation_time = table.Column(type: "datetime", nullable: false), - method = table.Column(type: "varchar(100)", maxLength: 100, nullable: true) - .Annotation("MySql:CharSet", "utf8"), - route = table.Column(type: "varchar(100)", maxLength: 100, nullable: true) - .Annotation("MySql:CharSet", "utf8"), - request_headers = table.Column(type: "json", nullable: true) - .Annotation("MySql:CharSet", "utf8"), - request_payload = table.Column(type: "text", nullable: false, collation: "utf8_general_ci") - .Annotation("MySql:CharSet", "utf8"), - response_headers = table.Column(type: "json", nullable: true) - .Annotation("MySql:CharSet", "utf8"), - response_payload = table.Column(type: "text", nullable: true, collation: "utf8_general_ci") - .Annotation("MySql:CharSet", "utf8"), - status = table.Column(type: "int", nullable: false), - tenant_id = table.Column(type: "int unsigned", nullable: false), - uid = table.Column(type: "varchar(36)", nullable: false, collation: "utf8_general_ci") - .Annotation("MySql:CharSet", "utf8"), - delivery = table.Column(type: "datetime", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PRIMARY", x => x.id); - table.ForeignKey( - name: "FK_webhooks_logs_webhooks_config_config_id", - column: x => x.config_id, - principalTable: "webhooks_config", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }) - .Annotation("MySql:CharSet", "utf8"); + migrationBuilder.CreateTable( + name: "webhooks_logs", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + config_id = table.Column(type: "int", nullable: false), + creation_time = table.Column(type: "datetime", nullable: false), + webhook_id = table.Column(type: "int", nullable: false), + request_headers = table.Column(type: "json", nullable: true) + .Annotation("MySql:CharSet", "utf8"), + request_payload = table.Column(type: "text", nullable: false, collation: "utf8_general_ci") + .Annotation("MySql:CharSet", "utf8"), + response_headers = table.Column(type: "json", nullable: true) + .Annotation("MySql:CharSet", "utf8"), + response_payload = table.Column(type: "text", nullable: true, collation: "utf8_general_ci") + .Annotation("MySql:CharSet", "utf8"), + status = table.Column(type: "int", nullable: false), + tenant_id = table.Column(type: "int unsigned", nullable: false), + uid = table.Column(type: "varchar(36)", nullable: false, collation: "utf8_general_ci") + .Annotation("MySql:CharSet", "utf8"), + delivery = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PRIMARY", x => x.id); + table.ForeignKey( + name: "FK_webhooks_logs_webhooks_config_config_id", + column: x => x.config_id, + principalTable: "webhooks_config", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8"); - migrationBuilder.CreateIndex( - name: "tenant_id", - table: "webhooks_config", - column: "tenant_id"); + migrationBuilder.CreateIndex( + name: "tenant_id", + table: "webhooks_config", + column: "tenant_id"); - migrationBuilder.CreateIndex( - name: "IX_webhooks_logs_config_id", - table: "webhooks_logs", - column: "config_id"); + migrationBuilder.CreateIndex( + name: "IX_webhooks_logs_config_id", + table: "webhooks_logs", + column: "config_id"); - migrationBuilder.CreateIndex( - name: "tenant_id", - table: "webhooks_logs", - column: "tenant_id"); - } + migrationBuilder.CreateIndex( + name: "tenant_id", + table: "webhooks_logs", + column: "tenant_id"); + } - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "webhooks_logs"); + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "webhooks"); - migrationBuilder.DropTable( - name: "webhooks_config"); - } + migrationBuilder.DropTable( + name: "webhooks_logs"); + + migrationBuilder.DropTable( + name: "webhooks_config"); } } diff --git a/migrations/mysql/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs b/migrations/mysql/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs index 13a232a63a..6c29cc2067 100644 --- a/migrations/mysql/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs +++ b/migrations/mysql/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs @@ -16,9 +16,40 @@ namespace ASC.Migrations.MySql.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("ProductVersion", "7.0.2") .HasAnnotation("Relational:MaxIdentifierLength", 64); + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Method") + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("method") + .HasDefaultValueSql("''") + .IsRequired(); + + b.Property("Route") + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasColumnName("route") + .HasDefaultValueSql("''") + .IsRequired(); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("webhooks", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b => { b.Property("Id") @@ -32,12 +63,6 @@ namespace ASC.Migrations.MySql.Migrations .HasColumnName("enabled") .HasDefaultValueSql("'1'"); - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)") - .HasColumnName("name"); - b.Property("SecretKey") .ValueGeneratedOnAdd() .HasMaxLength(50) @@ -88,7 +113,7 @@ namespace ASC.Migrations.MySql.Migrations b.Property("Method") .HasMaxLength(100) - .HasColumnType("varchar(100)") + .HasColumnType("varchar") .HasColumnName("method"); b.Property("RequestHeaders") @@ -112,10 +137,9 @@ namespace ASC.Migrations.MySql.Migrations .UseCollation("utf8_general_ci") .HasAnnotation("MySql:CharSet", "utf8"); - b.Property("Route") - .HasMaxLength(100) - .HasColumnType("varchar(100)") - .HasColumnName("route"); + b.Property("ConfigId") + .HasColumnType("int") + .HasColumnName("config_id"); b.Property("Status") .HasColumnType("int") diff --git a/migrations/postgre/WebhooksDbContext/20221019144344_WebhooksDbContextMigrate.Designer.cs b/migrations/postgre/WebhooksDbContext/20221019144344_WebhooksDbContextMigrate.Designer.cs index 158f206bee..91b697957e 100644 --- a/migrations/postgre/WebhooksDbContext/20221019144344_WebhooksDbContextMigrate.Designer.cs +++ b/migrations/postgre/WebhooksDbContext/20221019144344_WebhooksDbContextMigrate.Designer.cs @@ -21,7 +21,34 @@ namespace ASC.Migrations.PostgreSql.Migrations modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) .HasAnnotation("ProductVersion", "6.0.7") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Route") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("route"); + + b.Property("Method") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("method"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("webhooks", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b => { @@ -90,11 +117,6 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnType("datetime") .HasColumnName("delivery"); - b.Property("Method") - .HasMaxLength(100) - .HasColumnType("varchar") - .HasColumnName("method"); - b.Property("RequestHeaders") .HasColumnType("json") .HasColumnName("request_headers"); @@ -112,10 +134,9 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnType("text") .HasColumnName("response_payload"); - b.Property("Route") - .HasMaxLength(100) - .HasColumnType("varchar") - .HasColumnName("route"); + b.Property("WebhookId") + .HasColumnType("int") + .HasColumnName("webhook_id"); b.Property("Status") .HasColumnType("int") diff --git a/migrations/postgre/WebhooksDbContext/20221019144344_WebhooksDbContextMigrate.cs b/migrations/postgre/WebhooksDbContext/20221019144344_WebhooksDbContextMigrate.cs index c2830074a9..c5f6a6dd29 100644 --- a/migrations/postgre/WebhooksDbContext/20221019144344_WebhooksDbContextMigrate.cs +++ b/migrations/postgre/WebhooksDbContext/20221019144344_WebhooksDbContextMigrate.cs @@ -1,85 +1,126 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +using Microsoft.EntityFrameworkCore.Migrations; + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace ASC.Migrations.PostgreSql.Migrations +namespace ASC.Migrations.PostgreSql.Migrations; + +public partial class WebhooksDbContextMigrate : Migration { - public partial class WebhooksDbContextMigrate : Migration + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "webhooks", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + route = table.Column(type: "character varying(200)", maxLength: 200, nullable: false, defaultValueSql: "''"), + method = table.Column(type: "character varying(10)", maxLength: 10, nullable: false, defaultValueSql: "''") + }, + constraints: table => + { + table.PrimaryKey("PRIMARY", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "webhooks_config", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + secret_key = table.Column(type: "character varying(50)", maxLength: 50, nullable: true, defaultValueSql: "''"), + tenant_id = table.Column(type: "int unsigned", nullable: false), + uri = table.Column(type: "character varying(50)", maxLength: 50, nullable: true, defaultValueSql: "''"), + enabled = table.Column(type: "boolean", nullable: false, defaultValueSql: "true") + }, + constraints: table => + { + table.PrimaryKey("PRIMARY", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "webhooks_logs", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + config_id = table.Column(type: "int", nullable: false), + creation_time = table.Column(type: "datetime", nullable: false), + webhook_id = table.Column(type: "int", nullable: false), + request_headers = table.Column(type: "json", nullable: true), + request_payload = table.Column(type: "text", nullable: false), + response_headers = table.Column(type: "json", nullable: true), + response_payload = table.Column(type: "text", nullable: true), + status = table.Column(type: "int", nullable: false), + tenant_id = table.Column(type: "int unsigned", nullable: false), + uid = table.Column(type: "varchar", maxLength: 50, nullable: false), + delivery = table.Column(type: "datetime", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PRIMARY", x => x.id); + table.ForeignKey( + name: "FK_webhooks_logs_webhooks_config_config_id", + column: x => x.config_id, + principalTable: "webhooks_config", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "tenant_id", + table: "webhooks_config", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "IX_webhooks_logs_config_id", + table: "webhooks_logs", + column: "config_id"); + + migrationBuilder.CreateIndex( + name: "tenant_id", + table: "webhooks_logs", + column: "tenant_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "webhooks_config", - columns: table => new - { - id = table.Column(type: "int", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - secret_key = table.Column(type: "character varying(50)", maxLength: 50, nullable: true, defaultValueSql: "''"), - tenant_id = table.Column(type: "int unsigned", nullable: false), - uri = table.Column(type: "character varying(50)", maxLength: 50, nullable: true, defaultValueSql: "''"), - enabled = table.Column(type: "boolean", nullable: false, defaultValueSql: "true") - }, - constraints: table => - { - table.PrimaryKey("PRIMARY", x => x.id); - }); + migrationBuilder.DropTable( + name: "webhooks"); - migrationBuilder.CreateTable( - name: "webhooks_logs", - columns: table => new - { - id = table.Column(type: "int", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - config_id = table.Column(type: "int", nullable: false), - creation_time = table.Column(type: "datetime", nullable: false), - method = table.Column(type: "varchar", maxLength: 100, nullable: true), - route = table.Column(type: "varchar", maxLength: 100, nullable: true), - request_headers = table.Column(type: "json", nullable: true), - request_payload = table.Column(type: "text", nullable: false), - response_headers = table.Column(type: "json", nullable: true), - response_payload = table.Column(type: "text", nullable: true), - status = table.Column(type: "int", nullable: false), - tenant_id = table.Column(type: "int unsigned", nullable: false), - uid = table.Column(type: "varchar", maxLength: 50, nullable: false), - delivery = table.Column(type: "datetime", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PRIMARY", x => x.id); - table.ForeignKey( - name: "FK_webhooks_logs_webhooks_config_config_id", - column: x => x.config_id, - principalTable: "webhooks_config", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.DropTable( + name: "webhooks_logs"); - migrationBuilder.CreateIndex( - name: "tenant_id", - table: "webhooks_config", - column: "tenant_id"); - - migrationBuilder.CreateIndex( - name: "IX_webhooks_logs_config_id", - table: "webhooks_logs", - column: "config_id"); - - migrationBuilder.CreateIndex( - name: "tenant_id", - table: "webhooks_logs", - column: "tenant_id"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "webhooks_logs"); - - migrationBuilder.DropTable( - name: "webhooks_config"); - } + migrationBuilder.DropTable( + name: "webhooks_config"); } } diff --git a/migrations/postgre/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs b/migrations/postgre/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs index 0c5af441ed..f7b76268eb 100644 --- a/migrations/postgre/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs +++ b/migrations/postgre/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs @@ -18,9 +18,37 @@ namespace ASC.Migrations.PostgreSql.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("ProductVersion", "7.0.2") .HasAnnotation("Relational:MaxIdentifierLength", 63); + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Method") + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("method") + .HasDefaultValueSql("''"); + + b.Property("Route") + .ValueGeneratedOnAdd() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("route") + .HasDefaultValueSql("''"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("webhooks", (string)null); + }); + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b => { b.Property("Id") @@ -35,12 +63,6 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnName("enabled") .HasDefaultValueSql("true"); - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("name"); - b.Property("SecretKey") .ValueGeneratedOnAdd() .HasMaxLength(50) @@ -80,6 +102,10 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnType("int") .HasColumnName("config_id"); + b.Property("WebhookId") + .HasColumnType("int") + .HasColumnName("webhook_id"); + b.Property("CreationTime") .HasColumnType("datetime") .HasColumnName("creation_time"); @@ -88,11 +114,6 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnType("datetime") .HasColumnName("delivery"); - b.Property("Method") - .HasMaxLength(100) - .HasColumnType("varchar") - .HasColumnName("method"); - b.Property("RequestHeaders") .HasColumnType("json") .HasColumnName("request_headers"); @@ -110,11 +131,6 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnType("text") .HasColumnName("response_payload"); - b.Property("Route") - .HasMaxLength(100) - .HasColumnType("varchar") - .HasColumnName("route"); - b.Property("Status") .HasColumnType("int") .HasColumnName("status"); diff --git a/packages/client/public/locales/en/InviteDialog.json b/packages/client/public/locales/en/InviteDialog.json index 93e102fa45..68f6de8f6a 100644 --- a/packages/client/public/locales/en/InviteDialog.json +++ b/packages/client/public/locales/en/InviteDialog.json @@ -1,10 +1,14 @@ { "EmailErrorMessage": "Email address not valid. You can edit the email by clicking on it.", - "IndividualInvitation": "Individual invitation", "InviteAccountSearchPlaceholder": "Invite people by email", "InviteRoomSearchPlaceholder": "Invite people by name or email", - "InviteUsersToRoom": "Invite users to room", "Invited": "Invited", "LinkCopySuccess": "Link has been copied", - "SendInvitation": "Send invitation" + "SendInvitation": "Send invitation", + "InviteViaLink": "Invite via link", + "InviteViaLinkDescriptionRoom": "Create a universal link for self-authorization in the room", + "InviteViaLinkDescriptionAccounts": "Create a universal link for self-authorization in DocSpace", + "AddManually": "Add manually", + "AddManuallyDescriptionRoom": "Add existing DocSpace users to the room using the names or invite new users personally via email", + "AddManuallyDescriptionAccounts": "Invite new users to DocSpace personally via email" } diff --git a/packages/client/public/locales/ru/InviteDialog.json b/packages/client/public/locales/ru/InviteDialog.json index 1a14052280..180384aef0 100644 --- a/packages/client/public/locales/ru/InviteDialog.json +++ b/packages/client/public/locales/ru/InviteDialog.json @@ -1,10 +1,14 @@ { "EmailErrorMessage": "Адрес электронной почты недействителен. Вы можете отредактировать адрес, нажав на него.", - "IndividualInvitation": "Индивидуальное приглашение", "InviteAccountSearchPlaceholder": "Пригласить людей по электронной почте", "InviteRoomSearchPlaceholder": "Приглашайте людей по имени или электронной почте", - "InviteUsersToRoom": "Пригласить пользователей в комнату", "Invited": "Приглашен", "LinkCopySuccess": "Ссылка скопирована", - "SendInvitation": "Выслать приглашение" + "SendInvitation": "Выслать приглашение", + "InviteViaLink": "Пригласить по ссылке", + "InviteViaLinkDescriptionRoom": "Создать универсальную ссылку для самостоятельной авторизации в комнате", + "InviteViaLinkDescriptionAccounts": "Создать универсальную ссылку для самостоятельной авторизации в DocSpace", + "AddManually": "Добавить вручную", + "AddManuallyDescriptionRoom": "Добавьте существующих пользователей DocSpace в комнату используя имена или лично пригласите новых пользователей по электронной почте", + "AddManuallyDescriptionAccounts": "Приглашайте новых пользователей в DocSpace лично по электронной почте" } diff --git a/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js b/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js index b668e7b52c..7ded8a4ca5 100644 --- a/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js +++ b/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js @@ -49,7 +49,7 @@ const StyledSubHeader = styled(Heading)` font-size: 16px; padding-left: 16px; padding-right: 16px; - margin: 16px 0; + margin: 16px 0 8px 0; ${(props) => props.inline && @@ -60,6 +60,20 @@ const StyledSubHeader = styled(Heading)` `}; `; +const StyledDescription = styled(Text)` + padding-left: 16px; + padding-right: 16px; + color: ${(props) => + props.theme.createEditRoomDialog.commonParam.descriptionColor}; + margin-bottom: 16px; + + font-weight: 400; + font-size: 12px; + line-height: 16px; +`; + +StyledDescription.defaultProps = { theme: Base }; + const StyledRow = styled.div` width: calc(100% - 32px) !important; @@ -252,4 +266,5 @@ export { StyledAccessSelector, StyledToggleButton, StyledText, + StyledDescription, }; diff --git a/packages/client/src/components/panels/InvitePanel/index.js b/packages/client/src/components/panels/InvitePanel/index.js index 73ea21930d..72e5470cbe 100644 --- a/packages/client/src/components/panels/InvitePanel/index.js +++ b/packages/client/src/components/panels/InvitePanel/index.js @@ -199,9 +199,7 @@ const InvitePanel = ({ zIndex={310} > - - {roomId === -1 ? t("Common:InviteUsers") : t("InviteUsersToRoom")} - + {t("Common:InviteUsers")} - {t("SharingPanel:ExternalLink")} + {t("InviteViaLink")} {false && ( //TODO: Change to linksVisible after added link information from backend
+ + {roomId === -1 + ? t("InviteViaLinkDescriptionAccounts") + : t("InviteViaLinkDescriptionRoom")} + {linksVisible && ( diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/InviteInput.js b/packages/client/src/components/panels/InvitePanel/sub-components/InviteInput.js index c9f45b9f3b..0abf3fcd6d 100644 --- a/packages/client/src/components/panels/InvitePanel/sub-components/InviteInput.js +++ b/packages/client/src/components/panels/InvitePanel/sub-components/InviteInput.js @@ -21,6 +21,7 @@ import { StyledInviteInputContainer, StyledDropDown, SearchItemText, + StyledDescription, } from "../StyledInvitePanel"; const InviteInput = ({ @@ -223,7 +224,7 @@ const InviteInput = ({ return ( <> - {t("IndividualInvitation")} + {t("AddManually")} {!hideSelector && ( )} + + {roomId === -1 + ? t("AddManuallyDescriptionAccounts") + : t("AddManuallyDescriptionRoom")} + diff --git a/packages/client/src/pages/Home/InfoPanel/Body/styles/noItem.js b/packages/client/src/pages/Home/InfoPanel/Body/styles/noItem.js index 567a48cb3f..fd2bab838e 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/styles/noItem.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/styles/noItem.js @@ -18,8 +18,13 @@ const StyledNoItemContainer = styled.div` } .no-thumbnail-img-wrapper { - width: 96px; - height: 100px; + width: 75px; + height: 75px; + + img { + width: 75px; + height: 75px; + } } .no-accounts { diff --git a/packages/client/src/pages/Home/InfoPanel/Body/styles/severalItems.js b/packages/client/src/pages/Home/InfoPanel/Body/styles/severalItems.js index daa0e8e506..97ccf48113 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/styles/severalItems.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/styles/severalItems.js @@ -7,6 +7,11 @@ const StyledSeveralItemsContainer = styled.div` justify-content: center; gap: 32px; padding-top: ${(props) => (props.isAccounts ? "80px" : "0")}; + + img { + width: 75px; + height: 75px; + } `; export { StyledSeveralItemsContainer }; diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/SeveralItems/index.js b/packages/client/src/pages/Home/InfoPanel/Body/views/SeveralItems/index.js index 2dd55d6fc4..5bfd1c5106 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/views/SeveralItems/index.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/SeveralItems/index.js @@ -31,7 +31,7 @@ const SeveralItems = ({ isAccounts, theme, selectedItems }) => { isAccounts={isAccounts} className="no-thumbnail-img-wrapper" > - + {`${itemsText}: ${selectedItems.length}`} diff --git a/packages/client/src/pages/Home/Section/Body/TableView/TableHeader.js b/packages/client/src/pages/Home/Section/Body/TableView/TableHeader.js index e6603e3b7e..9b14f3adfb 100644 --- a/packages/client/src/pages/Home/Section/Body/TableView/TableHeader.js +++ b/packages/client/src/pages/Home/Section/Body/TableView/TableHeader.js @@ -14,7 +14,14 @@ class FilesTableHeader extends React.Component { } getTableColumns = (fromUpdate = false) => { - const { t, isRooms, isTrashFolder, getColumns } = this.props; + const { + t, + isRooms, + isTrashFolder, + getColumns, + columnStorageName, + columnInfoPanelStorageName, + } = this.props; const defaultColumns = []; @@ -224,11 +231,15 @@ class FilesTableHeader extends React.Component { this.setState({ columns, resetColumnsSize, + columnStorageName, + columnInfoPanelStorageName, }); } else { this.state = { columns, resetColumnsSize, + columnStorageName, + columnInfoPanelStorageName, }; } }; @@ -267,9 +278,18 @@ class FilesTableHeader extends React.Component { }; componentDidUpdate(prevProps) { + const { + isRooms, + isTrashFolder, + columnStorageName, + columnInfoPanelStorageName, + } = this.props; + if ( - this.props.isRooms !== prevProps.isRooms || - this.props.isTrashFolder !== prevProps.isTrashFolder + isRooms !== prevProps.isRooms || + isTrashFolder !== prevProps.isTrashFolder || + columnStorageName !== prevProps.columnStorageName || + columnInfoPanelStorageName !== prevProps.columnInfoPanelStorageName ) { return this.getTableColumns(true); } @@ -361,36 +381,22 @@ class FilesTableHeader extends React.Component { firstElemChecked, sortingVisible, infoPanelVisible, - columnStorageName, - filesColumnStorageName, - roomsColumnStorageName, - columnInfoPanelStorageName, - filesColumnInfoPanelStorageName, - roomsColumnInfoPanelStorageName, + withPaging, tagRef, setHideColumns, } = this.props; - const { columns, resetColumnsSize } = this.state; + const { + columns, + resetColumnsSize, + columnStorageName, + columnInfoPanelStorageName, + } = this.state; const sortBy = isRooms ? roomsFilter.sortBy : filter.sortBy; const sortOrder = isRooms ? roomsFilter.sortOrder : filter.sortOrder; - // TODO: make some better - let currentColumnStorageName = columnStorageName; - let currentColumnInfoPanelStorageName = columnInfoPanelStorageName; - - if (columns.length === 5 && columnStorageName === filesColumnStorageName) { - currentColumnStorageName = roomsColumnStorageName; - currentColumnInfoPanelStorageName = roomsColumnInfoPanelStorageName; - } - - if (columns.length === 7 && columnStorageName === roomsColumnStorageName) { - currentColumnStorageName = filesColumnStorageName; - currentColumnInfoPanelStorageName = filesColumnInfoPanelStorageName; - } - return ( { setIsTooltipLoading(false); }; + const canVisibleTitle = originRoomTitle || originTitle; + return [ { data-tip={""} data-place={"bottom"} > - {originRoomTitle || originTitle} + {originRoomTitle || originTitle || "—"} , { {t("Common:Name")} - {profile.displayName} + + {profile.displayName} +
p { padding-left: 8px; } diff --git a/packages/client/src/store/ContextOptionsStore.js b/packages/client/src/store/ContextOptionsStore.js index 7d2e7df659..6a2d0d2e5e 100644 --- a/packages/client/src/store/ContextOptionsStore.js +++ b/packages/client/src/store/ContextOptionsStore.js @@ -514,7 +514,7 @@ class ContextOptionsStore { } } - return options; + return options.filter((o) => !!o); }; onShowInfoPanel = (item) => { @@ -589,8 +589,8 @@ class ContextOptionsStore { const hasInfoPanel = contextOptions.includes("show-info"); - const emailSendIsDisabled = true; - const showSeparator0 = hasInfoPanel || !isMedia || !emailSendIsDisabled; + //const emailSendIsDisabled = true; + const showSeparator0 = hasInfoPanel || !isMedia; // || !emailSendIsDisabled; const separator0 = showSeparator0 ? { @@ -873,13 +873,13 @@ class ContextOptionsStore { onClick: () => this.onCopyLink(item, t), disabled: false, }, - { - id: "option_send-by-email", - key: "send-by-email", - label: t("SendByEmail"), - icon: MailReactSvgUrl, - disabled: emailSendIsDisabled, - }, + // { + // id: "option_send-by-email", + // key: "send-by-email", + // label: t("SendByEmail"), + // icon: MailReactSvgUrl, + // disabled: emailSendIsDisabled, + // }, ...versionActions, { id: "option_show-info", @@ -1048,6 +1048,15 @@ class ContextOptionsStore { }); } } + + if (options[0]?.isSeparator) { + options.shift(); + } + + if (options[options.length - 1]?.isSeparator) { + options.pop(); + } + return options; }; diff --git a/packages/client/src/store/FilesStore.js b/packages/client/src/store/FilesStore.js index b6762d586e..8f5bf7b9b5 100644 --- a/packages/client/src/store/FilesStore.js +++ b/packages/client/src/store/FilesStore.js @@ -177,12 +177,11 @@ class FilesStore { const fileInfo = await api.files.getFileInfo(file.id); + if (this.files.findIndex((x) => x.id === opt?.id) > -1) return; console.log("[WS] create new file", fileInfo.id, fileInfo.title); const newFiles = [fileInfo, ...this.files]; - if (this.files.findIndex((x) => x.id === opt?.id) > -1) return; - if (newFiles.length > this.filter.pageCount && withPaging) { newFiles.pop(); // Remove last } diff --git a/packages/client/src/store/UploadDataStore.js b/packages/client/src/store/UploadDataStore.js index 5c1cd4a883..15933fb8b6 100644 --- a/packages/client/src/store/UploadDataStore.js +++ b/packages/client/src/store/UploadDataStore.js @@ -21,6 +21,9 @@ import { isMobile as isMobileUtils, isTablet as isTabletUtils, } from "@docspace/components/utils/device"; + +const UPLOAD_LIMIT_AT_ONCE = 5; + class UploadDataStore { authStore; treeFoldersStore; @@ -45,6 +48,7 @@ class UploadDataStore { converted = true; uploadPanelVisible = false; selectedUploadFile = []; + tempFiles = []; errors = 0; isUploading = false; @@ -52,6 +56,11 @@ class UploadDataStore { isConvertSingleFile = false; + currentUploadNumber = 0; + uploadedFilesSize = 0; + + isParallel = true; + constructor( authStore, treeFoldersStore, @@ -125,6 +134,7 @@ class UploadDataStore { this.uploaded = true; this.converted = true; this.errors = 0; + this.uploadedFilesSize = 0; this.isUploadingAndConversion = false; this.isUploading = false; @@ -297,6 +307,19 @@ class UploadDataStore { const newPercent = ((uploadedSize + totalUploadedSize) / newTotalSize) * 100; + return newPercent; + }; + + getFilesPercent = (uploadedSize) => { + const newSize = this.uploadedFilesSize + uploadedSize; + this.uploadedFilesSize = newSize; + + const newTotalSize = sumBy(this.files, (f) => + f.file && !this.uploaded ? f.file.size : 0 + ); + + const newPercent = (newSize / newTotalSize) * 100; + /*console.log( `newPercent=${newPercent} (newTotalSize=${newTotalSize} totalUploadedSize=${totalUploadedSize} indexOfFile=${indexOfFile})` );*/ @@ -468,6 +491,7 @@ class UploadDataStore { file.error = error; file.convertProgress = progress; file.inConversion = false; + file.fileInfo = fileInfo; if (error.indexOf("password") !== -1) { file.needPassword = true; @@ -577,13 +601,36 @@ class UploadDataStore { if (this.uploaded && this.converted) { this.files = []; this.filesToConversion = []; + this.uploadedFilesSize = 0; } let newFiles = this.files; let filesSize = 0; let convertSize = 0; - for (let index of Object.keys(uploadFiles)) { + const uploadFilesArray = Object.keys(uploadFiles); + const hasFolder = + uploadFilesArray.findIndex((_, ind) => { + const file = uploadFiles[ind]; + + const filePath = file.path + ? file.path + : file.webkitRelativePath + ? file.webkitRelativePath + : file.name; + + return file.name !== filePath; + }) > -1; + + if (hasFolder) { + if (this.uploaded) this.isParallel = false; + else { + this.tempFiles.push({ uploadFiles, folderId, t }); + return; + } + } + + for (let index of uploadFilesArray) { const file = uploadFiles[index]; const parts = file.name.split("."); @@ -644,7 +691,9 @@ class UploadDataStore { converted: !!this.tempConversionFiles.length, }; - if (this.uploaded && countUploadingFiles) { + const isParallel = this.isParallel ? true : this.uploaded; + + if (isParallel && countUploadingFiles) { this.setUploadData(newUploadData); this.startUploadFiles(t); } @@ -779,15 +828,27 @@ class UploadDataStore { return Promise.reject(res.data.message); } - const fileId = res.data.data.id; + const { uploaded, id: fileId } = res.data.data; - const { uploaded } = res.data.data; + let uploadedSize, newPercent; - const uploadedSize = uploaded - ? fileSize - : index * this.settingsStore.chunkUploadSize; + if (!this.isParallel) { + uploadedSize = uploaded + ? fileSize + : index * this.settingsStore.chunkUploadSize; - const newPercent = this.getNewPercent(uploadedSize, indexOfFile); + newPercent = this.getNewPercent(uploadedSize, indexOfFile); + } else { + if (!uploaded) { + uploadedSize = + fileSize <= this.settingsStore.chunkUploadSize + ? fileSize + : this.settingsStore.chunkUploadSize; + } else { + uploadedSize = fileSize - index * this.settingsStore.chunkUploadSize; + } + newPercent = this.getFilesPercent(uploadedSize); + } const percentCurrentFile = (index / length) * 100; @@ -808,13 +869,21 @@ class UploadDataStore { this.files[indexOfFile].fileId = fileId; this.files[indexOfFile].fileInfo = fileInfo; this.percent = newPercent; + + if (this.isParallel) { + this.currentUploadNumber -= 1; + + const nextFileIndex = this.files.findIndex((f) => !f.inAction); + + if (nextFileIndex !== -1) { + this.startSessionFunc(nextFileIndex, t); + } + } }); if (fileInfo.version > 2) { this.filesStore.setUploadedFileIdWithVersion(fileInfo.id); } - - //setUploadData(uploadData); } } @@ -861,43 +930,71 @@ class UploadDataStore { this.primaryProgressDataStore.setPrimaryProgressBarData(progressData); - let index = 0; - let len = files.length; - while (index < len) { - await this.startSessionFunc(index, t); - index++; + if (this.isParallel) { + // console.log("IS PARALLEL"); + const notUploadedFiles = this.files.filter((f) => !f.inAction); - files = this.files; - len = files.length; - } + const countFiles = + notUploadedFiles.length >= UPLOAD_LIMIT_AT_ONCE + ? UPLOAD_LIMIT_AT_ONCE + : notUploadedFiles.length; - if (!this.filesToConversion.length) { - this.finishUploadFiles(); - } else { - runInAction(() => (this.uploaded = true)); - const uploadedFiles = this.files.filter((x) => x.action === "uploaded"); - const totalErrorsCount = sumBy(uploadedFiles, (f) => (f.error ? 1 : 0)); - if (totalErrorsCount > 0) - console.log("Upload errors: ", totalErrorsCount); - - setTimeout(() => { - if (!this.uploadPanelVisible && !totalErrorsCount && this.converted) { - this.clearUploadedFiles(); + for (let i = 0; i < countFiles; i++) { + if (this.currentUploadNumber <= UPLOAD_LIMIT_AT_ONCE) { + const fileIndex = this.files.findIndex( + (f) => f.uniqueId === notUploadedFiles[i].uniqueId + ); + if (fileIndex !== -1) { + this.currentUploadNumber += 1; + this.startSessionFunc(fileIndex, t); + } } - }, TIMEOUT); + } + } else { + // console.log("IS NOT PARALLEL"); + let index = 0; + let len = files.length; + while (index < len) { + await this.startSessionFunc(index, t); + index++; + + files = this.files; + len = files.length; + } + + if (!this.filesToConversion.length) { + this.finishUploadFiles(); + } else { + runInAction(() => { + this.uploaded = true; + this.isParallel = true; + }); + const uploadedFiles = this.files.filter((x) => x.action === "uploaded"); + const totalErrorsCount = sumBy(uploadedFiles, (f) => (f.error ? 1 : 0)); + if (totalErrorsCount > 0) + console.log("Upload errors: ", totalErrorsCount); + + setTimeout(() => { + if (!this.uploadPanelVisible && !totalErrorsCount && this.converted) { + this.clearUploadedFiles(); + } + }, TIMEOUT); + } } }; startSessionFunc = (indexOfFile, t) => { - //console.log("START UPLOAD SESSION FUNC", uploadData); + // console.log("START UPLOAD SESSION FUNC"); if (!this.uploaded && this.files.length === 0) { this.uploaded = true; + this.isParallel = true; //setUploadData(uploadData); return; } const item = this.files[indexOfFile]; + this.files[indexOfFile].inAction = true; if (!item) { console.error("Empty files"); @@ -949,15 +1046,17 @@ class UploadDataStore { return { location, requestsDataArray, fileSize, path }; }) .then(({ location, requestsDataArray, fileSize, path }) => { - this.primaryProgressDataStore.setPrimaryProgressBarData({ - icon: "upload", - visible: true, - percent: this.percent, - loadingFile: { - uniqueId: this.files[indexOfFile].uniqueId, - percent: chunks < 2 ? 50 : 0, - }, - }); + if (!this.isParallel) { + this.primaryProgressDataStore.setPrimaryProgressBarData({ + icon: "upload", + visible: true, + percent: this.percent, + loadingFile: { + uniqueId: this.files[indexOfFile].uniqueId, + percent: chunks < 2 ? 50 : 0, + }, + }); + } return this.uploadFileChunks( location, @@ -992,9 +1091,9 @@ class UploadDataStore { this.files[indexOfFile].error = errorMessage; - //dispatch(setUploadData(uploadData)); - - const newPercent = this.getNewPercent(fileSize, indexOfFile); + const newPercent = this.isParallel + ? this.getFilesPercent(fileSize) + : this.getNewPercent(fileSize, indexOfFile); this.primaryProgressDataStore.setPrimaryProgressBarData({ icon: "upload", @@ -1004,6 +1103,42 @@ class UploadDataStore { }); return Promise.resolve(); + }) + .finally(() => { + if (!this.isParallel) return; + + const allFilesIsUploaded = + this.files.findIndex((f) => f.action !== "uploaded" && !f.error) === + -1; + + if (allFilesIsUploaded) { + if (!this.filesToConversion.length) { + this.finishUploadFiles(); + } else { + runInAction(() => { + this.uploaded = true; + this.isParallel = true; + }); + const uploadedFiles = this.files.filter( + (x) => x.action === "uploaded" + ); + const totalErrorsCount = sumBy(uploadedFiles, (f) => + f.error ? 1 : 0 + ); + if (totalErrorsCount > 0) + console.log("Upload errors: ", totalErrorsCount); + + setTimeout(() => { + if ( + !this.uploadPanelVisible && + !totalErrorsCount && + this.converted + ) { + this.clearUploadedFiles(); + } + }, TIMEOUT); + } + } }); }; @@ -1011,6 +1146,21 @@ class UploadDataStore { const { fetchFiles, filter } = this.filesStore; const { withPaging } = this.authStore.settingsStore; + if (this.tempFiles.length) { + this.uploaded = true; + this.isParallel = true; + this.converted = true; + this.uploadedFilesSize = 0; + + for (let item of this.tempFiles) { + const { uploadFiles, folderId, t } = item; + this.startUpload(uploadFiles, folderId, t); + } + this.tempFiles = []; + + return; + } + const totalErrorsCount = sumBy(this.files, (f) => { f.error && toastr.error(f.error); return f.error ? 1 : 0; @@ -1025,7 +1175,9 @@ class UploadDataStore { } this.uploaded = true; + this.isParallel = true; this.converted = true; + this.uploadedFilesSize = 0; const uploadData = { filesSize: 0, diff --git a/packages/common/components/FilterInput/sub-components/FilterBlockItem.js b/packages/common/components/FilterInput/sub-components/FilterBlockItem.js index 476a4319af..5ac5c0e69d 100644 --- a/packages/common/components/FilterInput/sub-components/FilterBlockItem.js +++ b/packages/common/components/FilterInput/sub-components/FilterBlockItem.js @@ -105,6 +105,7 @@ const FilterBlockItem = ({ className="filter-text" noSelect={true} isSelected={item.isSelected} + truncate > {item?.selectedLabel?.toLowerCase()} diff --git a/products/ASC.Files/Core/Utils/FileMarker.cs b/products/ASC.Files/Core/Utils/FileMarker.cs index 7f9cf42528..63949d7a44 100644 --- a/products/ASC.Files/Core/Utils/FileMarker.cs +++ b/products/ASC.Files/Core/Utils/FileMarker.cs @@ -80,7 +80,7 @@ public class FileMarker private readonly AuthContext _authContext; private readonly IServiceProvider _serviceProvider; private readonly FilesSettingsHelper _filesSettingsHelper; - + private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); public FileMarker( TenantManager tenantManager, UserManager userManager, @@ -351,30 +351,41 @@ public class FileMarker var tagDao = _daoFactory.GetTagDao(); var newTags = new List(); var updateTags = new List(); - - foreach (var userID in userEntriesData.Keys) + + try { - if (await tagDao.GetNewTagsAsync(userID, obj.FileEntry).AnyAsync()) + await _semaphore.WaitAsync(); + + foreach (var userID in userEntriesData.Keys) { - continue; + if (await tagDao.GetNewTagsAsync(userID, obj.FileEntry).AnyAsync()) + { + continue; + } + + var entries = userEntriesData[userID].Distinct().ToList(); + + await GetNewTagsAsync(userID, entries.OfType>().ToList()); + await GetNewTagsAsync(userID, entries.OfType>().ToList()); + } + + if (updateTags.Count > 0) + { + await tagDao.UpdateNewTags(updateTags, obj.CurrentAccountId); + } + + if (newTags.Count > 0) + { + await tagDao.SaveTags(newTags, obj.CurrentAccountId); } - - var entries = userEntriesData[userID].Distinct().ToList(); - - await GetNewTagsAsync(userID, entries.OfType>().ToList()); - await GetNewTagsAsync(userID, entries.OfType>().ToList()); } - - - - if (updateTags.Count > 0) + catch { - await tagDao.UpdateNewTags(updateTags, obj.CurrentAccountId); + throw; } - - if (newTags.Count > 0) + finally { - await tagDao.SaveTags(newTags, obj.CurrentAccountId); + _semaphore.Release(); } await Task.WhenAll(ExecMarkAsNewRequest(updateTags.Concat(newTags), socketManager)); diff --git a/products/ASC.Files/Server/Api/ApiControllerBase.cs b/products/ASC.Files/Server/Api/ApiControllerBase.cs index 42fb6959dd..e4af09ab19 100644 --- a/products/ASC.Files/Server/Api/ApiControllerBase.cs +++ b/products/ASC.Files/Server/Api/ApiControllerBase.cs @@ -41,7 +41,7 @@ public abstract class ApiControllerBase : ControllerBase _fileDtoHelper = fileDtoHelper; } - public async Task GetFileEntryWrapperAsync(FileEntry r) + protected async Task GetFileEntryWrapperAsync(FileEntry r) { FileEntryDto wrapper = null; if (r.FileEntryType == FileEntryType.Folder) diff --git a/products/ASC.Files/Server/Api/SettingsController.cs b/products/ASC.Files/Server/Api/SettingsController.cs index 419c048326..dc3821f8d1 100644 --- a/products/ASC.Files/Server/Api/SettingsController.cs +++ b/products/ASC.Files/Server/Api/SettingsController.cs @@ -32,18 +32,24 @@ public class SettingsController : ApiControllerBase { private readonly FileStorageService _fileStorageServiceString; private readonly FilesSettingsHelper _filesSettingsHelper; - private readonly ProductEntryPoint _productEntryPoint; - + private readonly ProductEntryPoint _productEntryPoint; + private readonly SettingsManager _settingsManager; + private readonly TenantManager _tenantManager; + public SettingsController( FileStorageService fileStorageServiceString, FilesSettingsHelper filesSettingsHelper, ProductEntryPoint productEntryPoint, FolderDtoHelper folderDtoHelper, - FileDtoHelper fileDtoHelper) : base(folderDtoHelper, fileDtoHelper) + FileDtoHelper fileDtoHelper, + SettingsManager settingsManager, + TenantManager tenantManager) : base(folderDtoHelper, fileDtoHelper) { _fileStorageServiceString = fileStorageServiceString; _filesSettingsHelper = filesSettingsHelper; - _productEntryPoint = productEntryPoint; + _productEntryPoint = productEntryPoint; + _settingsManager = settingsManager; + _tenantManager = tenantManager; } /// diff --git a/products/ASC.Files/Server/Startup.cs b/products/ASC.Files/Server/Startup.cs index 7e7a91d607..1e8c623433 100644 --- a/products/ASC.Files/Server/Startup.cs +++ b/products/ASC.Files/Server/Startup.cs @@ -33,7 +33,7 @@ public class Startup : BaseStartup public Startup(IConfiguration configuration, IHostEnvironment hostEnvironment) : base(configuration, hostEnvironment) { - + WebhooksEnabled = true; } public override void ConfigureServices(IServiceCollection services) diff --git a/products/ASC.People/Server/Startup.cs b/products/ASC.People/Server/Startup.cs index 9042270187..1db625253c 100644 --- a/products/ASC.People/Server/Startup.cs +++ b/products/ASC.People/Server/Startup.cs @@ -32,6 +32,7 @@ public class Startup : BaseStartup public Startup(IConfiguration configuration, IHostEnvironment hostEnvironment) : base(configuration, hostEnvironment) { + WebhooksEnabled = true; } public override void ConfigureServices(IServiceCollection services) diff --git a/web/ASC.Web.Api/Api/ConnectionsController.cs b/web/ASC.Web.Api/Api/ConnectionsController.cs index f459fd0753..1e3a76a857 100644 --- a/web/ASC.Web.Api/Api/ConnectionsController.cs +++ b/web/ASC.Web.Api/Api/ConnectionsController.cs @@ -206,7 +206,7 @@ public class ConnectionsController : ControllerBase } } - public async Task LogOutAllActiveConnections(Guid? userId = null) + private async Task LogOutAllActiveConnections(Guid? userId = null) { var currentUserId = _securityContext.CurrentAccount.ID; var user = _userManager.GetUsers(userId ?? currentUserId); @@ -217,7 +217,7 @@ public class ConnectionsController : ControllerBase await _cookiesManager.ResetUserCookie(user.Id); } - public int GetLoginEventIdFromCookie() + private int GetLoginEventIdFromCookie() { var cookie = _cookiesManager.GetCookies(CookiesType.AuthKey); var loginEventId = _cookieStorage.GetLoginEventIdFromCookie(cookie); diff --git a/web/ASC.Web.Api/Api/Settings/RadicaleController.cs b/web/ASC.Web.Api/Api/Settings/RadicaleController.cs index 59188d2049..76f25d6aa9 100644 --- a/web/ASC.Web.Api/Api/Settings/RadicaleController.cs +++ b/web/ASC.Web.Api/Api/Settings/RadicaleController.cs @@ -169,7 +169,7 @@ public class RadicaleController : BaseSettingsController } - public string CardDavAllSerialization(Uri uri) + private string CardDavAllSerialization(Uri uri) { var builder = new StringBuilder(); var users = _userManager.GetUsers(); @@ -182,7 +182,7 @@ public class RadicaleController : BaseSettingsController return builder.ToString(); } - public static CardDavItem ItemFromUserInfo(UserInfo u) + private static CardDavItem ItemFromUserInfo(UserInfo u) { return new CardDavItem(u.Id, u.FirstName, u.LastName, u.UserName, u.BirthDate, u.Sex, u.Title, u.Email, u.ContactsList, u.MobilePhone); } diff --git a/web/ASC.Web.Api/Api/Settings/WebhooksController.cs b/web/ASC.Web.Api/Api/Settings/WebhooksController.cs index 6ad3868ea6..d1747ab4a4 100644 --- a/web/ASC.Web.Api/Api/Settings/WebhooksController.cs +++ b/web/ASC.Web.Api/Api/Settings/WebhooksController.cs @@ -23,7 +23,8 @@ // All the Product's GUI elements, including illustrations and icon sets, as well as technical writing // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode - + + namespace ASC.Web.Api.Controllers.Settings; public class WebhooksController : BaseSettingsController @@ -33,6 +34,7 @@ public class WebhooksController : BaseSettingsController private readonly DbWorker _webhookDbWorker; private readonly IMapper _mapper; private readonly WebhookPublisher _webhookPublisher; + private readonly SettingsManager _settingsManager; public WebhooksController( ApiContext context, @@ -43,7 +45,8 @@ public class WebhooksController : BaseSettingsController DbWorker dbWorker, IHttpContextAccessor httpContextAccessor, IMapper mapper, - WebhookPublisher webhookPublisher) + WebhookPublisher webhookPublisher, + SettingsManager settingsManager) : base(apiContext, memoryCache, webItemManager, httpContextAccessor) { _context = context; @@ -51,6 +54,7 @@ public class WebhooksController : BaseSettingsController _webhookDbWorker = dbWorker; _mapper = mapper; _webhookPublisher = webhookPublisher; + _settingsManager = settingsManager; } [HttpGet("webhook")] @@ -72,7 +76,7 @@ public class WebhooksController : BaseSettingsController ArgumentNullException.ThrowIfNull(model.Uri); ArgumentNullException.ThrowIfNull(model.SecretKey); - var webhook = await _webhookDbWorker.AddWebhookConfig(model.Name, model.Uri, model.SecretKey); + var webhook = await _webhookDbWorker.AddWebhookConfig(model.Uri, model.SecretKey); return _mapper.Map(webhook); } @@ -85,7 +89,7 @@ public class WebhooksController : BaseSettingsController ArgumentNullException.ThrowIfNull(model.Uri); ArgumentNullException.ThrowIfNull(model.SecretKey); - var webhook = await _webhookDbWorker.UpdateWebhookConfig(model.Id, model.Name, model.Uri, model.SecretKey, model.Enabled); + var webhook = await _webhookDbWorker.UpdateWebhookConfig(model.Id, model.Uri, model.SecretKey, model.Enabled); return _mapper.Map(webhook); } @@ -107,7 +111,7 @@ public class WebhooksController : BaseSettingsController var startIndex = Convert.ToInt32(_context.StartIndex); var count = Convert.ToInt32(_context.Count); - await foreach (var j in _webhookDbWorker.ReadJournal(startIndex, count, model.Delivery, model.Hookname, model.Route)) + await foreach (var j in _webhookDbWorker.ReadJournal(startIndex, count, model.Delivery, model.HookUri, model.WebhookId)) { yield return _mapper.Map(j); } @@ -135,7 +139,7 @@ public class WebhooksController : BaseSettingsController throw new HttpException(HttpStatusCode.Forbidden); } - var result = await _webhookPublisher.PublishAsync(item.Method, item.Route, item.RequestPayload, item.ConfigId); + var result = await _webhookPublisher.PublishAsync(item.Id, item.RequestPayload, item.ConfigId); return _mapper.Map(result); } @@ -154,9 +158,63 @@ public class WebhooksController : BaseSettingsController continue; } - var result = await _webhookPublisher.PublishAsync(item.Method, item.Route, item.RequestPayload, item.ConfigId); + var result = await _webhookPublisher.PublishAsync(item.Id, item.RequestPayload, item.ConfigId); yield return _mapper.Map(result); } - } + } + + [HttpGet("webhook/ssl")] + public WebhooksSslSettingsDto GetSslSettings() + { + _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings); + + var settings = _settingsManager.Load(); + + return _mapper.Map(settings); + } + + [HttpPost("webhook/ssl/{isEnabled}")] + public WebhooksSslSettingsDto SetSslSettings(bool isEnabled) + { + _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings); + + var settings = _settingsManager.Load(); + settings.EnableSSLVerification = isEnabled; + _settingsManager.Save(settings); + + return _mapper.Map(settings); + } + + [HttpGet("webhooks")] + public async IAsyncEnumerable Settings() + { + var settings = _settingsManager.Load(); + + foreach (var w in await _webhookDbWorker.GetWebhooksAsync()) + { + w.Disable = settings.Ids.Contains(w.Id); + yield return w; + } + } + + [HttpPut("webhook/{id}")] + public async Task DisableWebHook(int id) + { + var settings = _settingsManager.Load(); + + Webhook result = null; + + if (!settings.Ids.Contains(id) && (result = await _webhookDbWorker.GetWebhookAsync(id)) != null) + { + settings.Ids.Add(id); + } + + if (result != null) + { + _settingsManager.Save(settings); + } + + return result; + } } \ No newline at end of file diff --git a/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksConfigRequestsDto.cs b/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksConfigRequestsDto.cs index 12656d073e..11426d79c5 100644 --- a/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksConfigRequestsDto.cs +++ b/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksConfigRequestsDto.cs @@ -29,7 +29,6 @@ namespace ASC.Web.Api.ApiModels.RequestsDto; public class WebhooksConfigRequestsDto { public int Id { get; set; } - public string Name { get; set; } public string Uri { get; set; } public string SecretKey { get; set; } public bool? Enabled { get; set; } diff --git a/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksLogRequest.cs b/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksLogRequest.cs index 7d4d0035c6..94c1de7cbe 100644 --- a/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksLogRequest.cs +++ b/web/ASC.Web.Api/ApiModels/RequestsDto/WebhooksLogRequest.cs @@ -30,7 +30,7 @@ public class WebhooksLogRequest { public DateTime? Delivery { get; set; } - public string Hookname { get; set; } + public string HookUri { get; set; } - public string Route { get; set; } + public int WebhookId { get; set; } } diff --git a/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksConfigDto.cs b/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksConfigDto.cs index 626204763c..92e7a77a86 100644 --- a/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksConfigDto.cs +++ b/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksConfigDto.cs @@ -29,7 +29,6 @@ namespace ASC.Web.Api.ApiModels.ResponseDto; public class WebhooksConfigDto : IMapFrom { public int Id { get; set; } - public string Name { get; set; } public string Uri { get; set; } public string SecretKey { get; set; } public bool Enabled { get; set; } diff --git a/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksSslSettingsDto.cs b/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksSslSettingsDto.cs new file mode 100644 index 0000000000..42635de8ed --- /dev/null +++ b/web/ASC.Web.Api/ApiModels/ResponseDto/WebhooksSslSettingsDto.cs @@ -0,0 +1,32 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Web.Api.ApiModels.ResponseDto; + +public class WebhooksSslSettingsDto : IMapFrom +{ + public bool IsEnabled { get; set; } +} diff --git a/web/ASC.Web.Api/Startup.cs b/web/ASC.Web.Api/Startup.cs index a3d4aaf25f..f44a4b6484 100644 --- a/web/ASC.Web.Api/Startup.cs +++ b/web/ASC.Web.Api/Startup.cs @@ -32,7 +32,7 @@ public class Startup : BaseStartup public Startup(IConfiguration configuration, IHostEnvironment hostEnvironment) : base(configuration, hostEnvironment) { - + WebhooksEnabled = true; } public override void ConfigureServices(IServiceCollection services) diff --git a/web/ASC.Web.Studio/ASC.Web.Studio.csproj b/web/ASC.Web.Studio/ASC.Web.Studio.csproj index 155cc7eb53..3b4677ceb5 100644 --- a/web/ASC.Web.Studio/ASC.Web.Studio.csproj +++ b/web/ASC.Web.Studio/ASC.Web.Studio.csproj @@ -13,8 +13,13 @@ false + + + + + diff --git a/web/ASC.Web.Studio/GlobalUsings.cs b/web/ASC.Web.Studio/GlobalUsings.cs index e25d8f267e..7dde7435a3 100644 --- a/web/ASC.Web.Studio/GlobalUsings.cs +++ b/web/ASC.Web.Studio/GlobalUsings.cs @@ -24,13 +24,30 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +global using System.Collections.Concurrent; +global using System.Net.Security; +global using System.Security.Cryptography; +global using System.Text; +global using System.Text.Json; +global using System.Text.Json.Serialization; + global using ASC.Api.Core; global using ASC.Api.Core.Extensions; +global using ASC.Common; +global using ASC.Common.Caching; +global using ASC.Common.Log; +global using ASC.Common.Utils; +global using ASC.Core.Common.Settings; global using ASC.Data.Storage; global using ASC.FederatedLogin; global using ASC.FederatedLogin.LoginProviders; global using ASC.Web.Core.HttpHandlers; global using ASC.Web.Studio; +global using ASC.Web.Webhooks; +global using ASC.Webhooks; +global using ASC.Webhooks.Core; +global using ASC.Webhooks.Service.Log; +global using ASC.Webhooks.Service.Services; global using Autofac; @@ -38,3 +55,7 @@ global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.HttpOverrides; global using Microsoft.Extensions.Hosting.WindowsServices; +global using Microsoft.Extensions.Logging; + +global using Polly; +global using Polly.Extensions.Http; diff --git a/web/ASC.Web.Studio/Program.cs b/web/ASC.Web.Studio/Program.cs index 719e6cbb67..c9221272da 100644 --- a/web/ASC.Web.Studio/Program.cs +++ b/web/ASC.Web.Studio/Program.cs @@ -24,6 +24,8 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +using ASC.Webhooks.Extension; + using NLog; var options = new WebApplicationOptions @@ -35,6 +37,7 @@ var options = new WebApplicationOptions var builder = WebApplication.CreateBuilder(options); builder.Configuration.AddDefaultConfiguration(builder.Environment) + .AddWebhookConfiguration() .AddEnvironmentVariables() .AddCommandLine(args); @@ -56,10 +59,7 @@ try startup.ConfigureServices(builder.Services); - builder.Host.ConfigureContainer(containerBuilder => - { - startup.ConfigureContainer(containerBuilder); - }); + builder.Host.ConfigureContainer(startup.ConfigureContainer); var app = builder.Build(); @@ -86,5 +86,5 @@ finally public partial class Program { public static string Namespace = typeof(Startup).Namespace; - public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1).Replace(".",""); + public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1).Replace(".", ""); } \ No newline at end of file diff --git a/web/ASC.Web.Studio/Startup.cs b/web/ASC.Web.Studio/Startup.cs index ebb59d2a9c..14e2b74b28 100644 --- a/web/ASC.Web.Studio/Startup.cs +++ b/web/ASC.Web.Studio/Startup.cs @@ -76,5 +76,37 @@ public class Startup : BaseStartup DIHelper.TryAdd(); DIHelper.TryAdd(); DIHelper.TryAdd(); + + + services.AddHttpClient(); + + DIHelper.TryAdd(); + + services.AddHostedService(); + DIHelper.TryAdd(); + + services.AddHttpClient("webhook") + .SetHandlerLifetime(TimeSpan.FromMinutes(5)) + .AddPolicyHandler((s, request) => + { + var settings = s.GetRequiredService(); + + return HttpPolicyExtensions + .HandleTransientHttpError() + .Or() + .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) + .WaitAndRetryAsync(settings.RepeatCount.HasValue ? settings.RepeatCount.Value : 5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); + }) + .ConfigurePrimaryHttpMessageHandler((s) => + { + return new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => + { + var helper = s.GetRequiredService(); + return helper.ValidateCertificate(sslPolicyErrors); + } + }; + }); } } diff --git a/common/services/ASC.Webhooks.Service/Extensions/ConfigurationManagerExtension.cs b/web/ASC.Web.Studio/WebHooks/Extensions/ConfigurationManagerExtension.cs similarity index 100% rename from common/services/ASC.Webhooks.Service/Extensions/ConfigurationManagerExtension.cs rename to web/ASC.Web.Studio/WebHooks/Extensions/ConfigurationManagerExtension.cs diff --git a/common/services/ASC.Webhooks.Service/Log/WebhookSenderLogger.cs b/web/ASC.Web.Studio/WebHooks/Log/WebhookSenderLogger.cs similarity index 92% rename from common/services/ASC.Webhooks.Service/Log/WebhookSenderLogger.cs rename to web/ASC.Web.Studio/WebHooks/Log/WebhookSenderLogger.cs index f184e25f42..59de197980 100644 --- a/common/services/ASC.Webhooks.Service/Log/WebhookSenderLogger.cs +++ b/web/ASC.Web.Studio/WebHooks/Log/WebhookSenderLogger.cs @@ -29,4 +29,7 @@ internal static partial class WebhookSenderLogger { [LoggerMessage(Level = LogLevel.Debug, Message = "Procedure: Finish.")] public static partial void DebugProcedureFinish(this ILogger logger); + + [LoggerMessage(Level = LogLevel.Error)] + public static partial void ErrorSSLVerification(this ILogger logger, Exception exception); } diff --git a/common/services/ASC.Webhooks.Service/Log/WorkerServiceLogger.cs b/web/ASC.Web.Studio/WebHooks/Log/WorkerServiceLogger.cs similarity index 100% rename from common/services/ASC.Webhooks.Service/Log/WorkerServiceLogger.cs rename to web/ASC.Web.Studio/WebHooks/Log/WorkerServiceLogger.cs diff --git a/common/services/ASC.Webhooks.Service/Services/WorkerService.cs b/web/ASC.Web.Studio/WebHooks/Services/WorkerService.cs similarity index 100% rename from common/services/ASC.Webhooks.Service/Services/WorkerService.cs rename to web/ASC.Web.Studio/WebHooks/Services/WorkerService.cs diff --git a/common/services/ASC.Webhooks.Service/Settings.cs b/web/ASC.Web.Studio/WebHooks/Settings.cs similarity index 100% rename from common/services/ASC.Webhooks.Service/Settings.cs rename to web/ASC.Web.Studio/WebHooks/Settings.cs diff --git a/common/services/ASC.Webhooks.Service/Startup.cs b/web/ASC.Web.Studio/WebHooks/SslHelper.cs similarity index 60% rename from common/services/ASC.Webhooks.Service/Startup.cs rename to web/ASC.Web.Studio/WebHooks/SslHelper.cs index db7a133ac6..adc243d6de 100644 --- a/common/services/ASC.Webhooks.Service/Startup.cs +++ b/web/ASC.Web.Studio/WebHooks/SslHelper.cs @@ -24,36 +24,46 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + namespace ASC.Webhooks; -public class Startup : BaseWorkerStartup +[Scope] +public class SslHelper { - public Startup(IConfiguration configuration, IHostEnvironment hostEnvironment) : - base(configuration, hostEnvironment) + private readonly SettingsManager _settingsManager; + + public SslHelper( + SettingsManager settingsManager + ) { - + _settingsManager = settingsManager; } - public override void ConfigureServices(IServiceCollection services) + public bool ValidateCertificate(SslPolicyErrors sslPolicyErrors) { - base.ConfigureServices(services); - - services.AddHttpClient(); - - DIHelper.TryAdd(); - - services.AddHostedService(); - DIHelper.TryAdd(); - - services.AddHttpClient("webhook") - .SetHandlerLifetime(TimeSpan.FromMinutes(5)) - .AddPolicyHandler((s, request) => + if (sslPolicyErrors == SslPolicyErrors.None) { - var settings = s.GetRequiredService(); + return true; + } - return HttpPolicyExtensions - .HandleTransientHttpError() - .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) - .WaitAndRetryAsync(settings.RepeatCount.HasValue ? settings.RepeatCount.Value : 5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); - }); + var settings = _settingsManager.Load(); + + if (!settings.EnableSSLVerification) + { + return true; + } + + throw new SslException(sslPolicyErrors); } } + +public class SslException : Exception +{ + public SslPolicyErrors Errors { get; set; } + public override string Message { get => Errors.ToString(); } + + public SslException(SslPolicyErrors errors) + { + Errors = errors; + } +} + diff --git a/common/services/ASC.Webhooks.Service/WebhookSender.cs b/web/ASC.Web.Studio/WebHooks/WebhookSender.cs similarity index 80% rename from common/services/ASC.Webhooks.Service/WebhookSender.cs rename to web/ASC.Web.Studio/WebHooks/WebhookSender.cs index 5b6b2d0ed4..1f6b51bd47 100644 --- a/common/services/ASC.Webhooks.Service/WebhookSender.cs +++ b/web/ASC.Web.Studio/WebHooks/WebhookSender.cs @@ -23,8 +23,8 @@ // All the Product's GUI elements, including illustrations and icon sets, as well as technical writing // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode - -using System.Text.Json.Serialization; + +using System.Net; namespace ASC.Webhooks; @@ -35,6 +35,7 @@ public class WebhookSender private readonly ILogger _log; private readonly IServiceScopeFactory _scopeFactory; private readonly JsonSerializerOptions _jsonSerializerOptions; + private const string SignatureHeader = "x-docspace-signature-256"; public WebhookSender(ILoggerProvider options, IServiceScopeFactory scopeFactory, IHttpClientFactory clientFactory) { @@ -70,7 +71,7 @@ public class WebhookSender }; request.Headers.Add("Accept", "*/*"); - request.Headers.Add("Secret", "SHA256=" + GetSecretHash(entry.Config.SecretKey, entry.RequestPayload)); + request.Headers.Add(SignatureHeader, $"sha256={GetSecretHash(entry.Config.SecretKey, entry.RequestPayload)}"); requestHeaders = JsonSerializer.Serialize(request.Headers.ToDictionary(r => r.Key, v => v.Value), _jsonSerializerOptions); var response = await httpClient.SendAsync(request, cancellationToken); @@ -78,40 +79,49 @@ public class WebhookSender status = (int)response.StatusCode; responseHeaders = JsonSerializer.Serialize(response.Headers.ToDictionary(r => r.Key, v => v.Value), _jsonSerializerOptions); responsePayload = await response.Content.ReadAsStringAsync(); - delivery = DateTime.UtcNow; _log.DebugResponse(response); } + catch (SslException e) + { + status = (int)e.Errors; + responsePayload = e.Message; + + _log.ErrorSSLVerification(e); + } catch (HttpRequestException e) { if (e.StatusCode.HasValue) { status = (int)e.StatusCode.Value; } + + //if (e.InnerException is SocketException se) + //{ + // status = (int)se.SocketErrorCode; + //} + responsePayload = e.Message; - delivery = DateTime.UtcNow; _log.ErrorWithException(e); } catch (Exception e) { + status = (int)HttpStatusCode.InternalServerError; _log.ErrorWithException(e); } - if (delivery != DateTime.MinValue) - { - await dbWorker.UpdateWebhookJournal(entry.Id, status, delivery, requestHeaders, responsePayload, responseHeaders); - } + delivery = DateTime.UtcNow; + + await dbWorker.UpdateWebhookJournal(entry.Id, status, delivery, requestHeaders, responsePayload, responseHeaders); } private string GetSecretHash(string secretKey, string body) { var secretBytes = Encoding.UTF8.GetBytes(secretKey); - - using (var hasher = new HMACSHA256(secretBytes)) - { - var data = Encoding.UTF8.GetBytes(body); - return BitConverter.ToString(hasher.ComputeHash(data)); - } + using var hasher = new HMACSHA256(secretBytes); + var data = Encoding.UTF8.GetBytes(body); + var hash = hasher.ComputeHash(data); + return Convert.ToHexString(hash); } } \ No newline at end of file