Merge branch 'develop' into feature/webhooks-ui

This commit is contained in:
Alexey Safronov 2023-02-22 18:57:50 +04:00
commit a6c730308d
82 changed files with 1638 additions and 710 deletions

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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()
{

View File

@ -76,13 +76,59 @@ public class CustomEndpointDataSource : EndpointDataSource
public static class EndpointExtension
{
public static IEndpointRouteBuilder MapCustom(this IEndpointRouteBuilder endpoints)
private static readonly IReadOnlyList<string> _methodList = new List<string>
{
"POST",
"PUT",
"DELETE"
};
public static async Task<IEndpointRouteBuilder> 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<IEndpointRouteBuilder> RegisterWebhooks(this IEndpointRouteBuilder endpoints, IServiceProvider serviceProvider)
{
var toRegister = endpoints.DataSources.First().Endpoints
.Cast<RouteEndpoint>()
.SelectMany(r =>
{
var result = new List<Webhook>();
var httpMethodMetadata = r.Metadata.OfType<HttpMethodMetadata>().FirstOrDefault();
var disabled = r.Metadata.OfType<WebhookDisableAttribute>().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<DbWorker>();
if (dbWorker != null)
{
await dbWorker.Register(toRegister);
}
return endpoints;
}
}

View File

@ -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
{
}

View File

@ -33,31 +33,33 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable
private Stream _bodyStream;
private readonly IWebhookPublisher _webhookPublisher;
private readonly ILogger<WebhooksGlobalFilterAttribute> _logger;
private static readonly List<string> _methodList = new List<string> { "POST", "UPDATE", "DELETE" };
private readonly SettingsManager _settingsManager;
private readonly DbWorker _dbWorker;
public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher, ILogger<WebhooksGlobalFilterAttribute> logger)
public WebhooksGlobalFilterAttribute(
IWebhookPublisher webhookPublisher,
ILogger<WebhooksGlobalFilterAttribute> 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<bool> 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<WebHooksSettings>().Ids.Contains(webhook.Id))
{
return true;
}

View File

@ -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<S3Storage> 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,

View File

@ -37,4 +37,19 @@
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\WebHookResource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>WebHookResource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\WebHookResource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>WebHookResource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -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<WebhooksDbContext> _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<WebhooksDbContext> dbContextFactory, TenantManager tenantManager, AuthContext authContext)
public DbWorker(
IDbContextFactory<WebhooksDbContext> dbContextFactory,
TenantManager tenantManager,
AuthContext authContext,
IMapper mapper)
{
_dbContextFactory = dbContextFactory;
_tenantManager = tenantManager;
_authContext = authContext;
_mapper = mapper;
}
public async Task<WebhooksConfig> AddWebhookConfig(string name, string uri, string secretKey)
public async Task<WebhooksConfig> 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<WebhooksConfig> UpdateWebhookConfig(int id, string name, string uri, string key, bool? enabled)
public async Task<WebhooksConfig> 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<WebhooksLog> ReadJournal(int startIndex, int limit, DateTime? delivery, string hookname, string route)
public IAsyncEnumerable<WebhooksLog> 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<Webhook> 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<DbWebhook>(webhook));
await webhooksDbContext.SaveChangesAsync();
}
catch (Exception)
{
}
}
}
}
public async Task<List<Webhook>> GetWebhooksAsync()
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var webHooks = await webhooksDbContext.Webhooks.AsNoTracking().ToListAsync();
return _mapper.Map<List<DbWebhook>, List<Webhook>>(webHooks);
}
public async Task<Webhook> GetWebhookAsync(int id)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var webHook = await webhooksDbContext.Webhooks.Where(r => r.Id == id).AsNoTracking().FirstOrDefaultAsync();
return _mapper.Map<DbWebhook, Webhook>(webHook);
}
public async Task<Webhook> 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<DbWebhook, Webhook>(webHook);
}
}

View File

@ -29,14 +29,16 @@ namespace ASC.Webhooks.Core.EF.Context;
public class WebhooksDbContext : DbContext
{
public DbSet<WebhooksConfig> WebhooksConfigs { get; set; }
public DbSet<WebhooksLog> WebhooksLogs { get; set; }
public DbSet<WebhooksLog> WebhooksLogs { get; set; }
public DbSet<DbWebhook> Webhooks { get; set; }
public WebhooksDbContext(DbContextOptions<WebhooksDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
ModelBuilderWrapper
.From(modelBuilder, Database)
.From(modelBuilder, Database)
.AddDbWebhooks()
.AddWebhooksConfig()
.AddWebhooksLog();
}

View File

@ -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<Webhook>
{
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<DbWebhook>(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<DbWebhook>(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("''");
});
}
}

View File

@ -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")

View File

@ -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")

View File

@ -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;

View File

@ -29,6 +29,6 @@ namespace ASC.Webhooks.Core;
[Scope]
public interface IWebhookPublisher
{
public Task PublishAsync(string method, string route, string requestPayload);
public Task<WebhooksLog> PublishAsync(string method, string route, string requestPayload, int configId);
public Task PublishAsync(int webhookId, string requestPayload);
public Task<WebhooksLog> PublishAsync(int webhookId, string requestPayload, int configId);
}

View File

@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ASC.Webhooks.Core.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Start Edit.
/// </summary>
internal static string POST_api_2_0_files_file__fileId__startedit {
get {
return ResourceManager.GetString("POST|api/2.0/files/file/{fileId}/startedit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Start Edit Description.
/// </summary>
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);
}
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="POST|api/2.0/files/file/{fileId}/startedit" xml:space="preserve">
<value>Start Edit</value>
</data>
<data name="POST|api/2.0/files/file/{fileId}/startedit_Description" xml:space="preserve">
<value>Start Edit Description</value>
</data>
</root>

View File

@ -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<WebHooksSettings>
{
public bool EnableSSLVerification { get; set; }
public List<int> 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;
public WebHooksSettings GetDefault() => new WebHooksSettings()
{
EnableSSLVerification = true,
Ids = new List<int> { }
};
}

View File

@ -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<DbWebhook>
{
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}"; }
}

View File

@ -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<WebhooksLog> PublishAsync(string method, string route, string requestPayload, int configId)
public async Task<WebhooksLog> 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

View File

@ -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"
}
]
}

View File

@ -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": [

View File

@ -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;

View File

@ -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
{

View File

@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ASC.Api.Core\ASC.Api.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -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(".", "");
}

View File

@ -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"
}
}
}

View File

@ -1,3 +0,0 @@
{
"pathToConf": "..\\..\\..\\config"
}

View File

@ -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"
}
},

View File

@ -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"
}
},

View File

@ -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"
}
},

View File

@ -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"
}
},

View File

@ -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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Route")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("varchar(200)")
.HasColumnName("route");
b.Property<string>("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<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("method");
b.Property<string>("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<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("route");
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<int>("Status")
.HasColumnType("int")

View File

@ -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<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
route = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8"),
method = table.Column<string>(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<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8"),
secret_key = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8"),
tenant_id = table.Column<uint>(type: "int unsigned", nullable: false),
uri = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8"),
enabled = table.Column<bool>(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<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8"),
secret_key = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8"),
tenant_id = table.Column<uint>(type: "int unsigned", nullable: false),
uri = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: true, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8"),
enabled = table.Column<bool>(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<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
config_id = table.Column<int>(type: "int", nullable: false),
creation_time = table.Column<DateTime>(type: "datetime", nullable: false),
method = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8"),
route = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8"),
request_headers = table.Column<string>(type: "json", nullable: true)
.Annotation("MySql:CharSet", "utf8"),
request_payload = table.Column<string>(type: "text", nullable: false, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
response_headers = table.Column<string>(type: "json", nullable: true)
.Annotation("MySql:CharSet", "utf8"),
response_payload = table.Column<string>(type: "text", nullable: true, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
status = table.Column<int>(type: "int", nullable: false),
tenant_id = table.Column<uint>(type: "int unsigned", nullable: false),
uid = table.Column<string>(type: "varchar(36)", nullable: false, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
delivery = table.Column<DateTime>(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<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
config_id = table.Column<int>(type: "int", nullable: false),
creation_time = table.Column<DateTime>(type: "datetime", nullable: false),
webhook_id = table.Column<int>(type: "int", nullable: false),
request_headers = table.Column<string>(type: "json", nullable: true)
.Annotation("MySql:CharSet", "utf8"),
request_payload = table.Column<string>(type: "text", nullable: false, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
response_headers = table.Column<string>(type: "json", nullable: true)
.Annotation("MySql:CharSet", "utf8"),
response_payload = table.Column<string>(type: "text", nullable: true, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
status = table.Column<int>(type: "int", nullable: false),
tenant_id = table.Column<uint>(type: "int unsigned", nullable: false),
uid = table.Column<string>(type: "varchar(36)", nullable: false, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
delivery = table.Column<DateTime>(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");
}
}

View File

@ -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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Method")
.ValueGeneratedOnAdd()
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnName("method")
.HasDefaultValueSql("''")
.IsRequired();
b.Property<string>("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<int>("Id")
@ -32,12 +63,6 @@ namespace ASC.Migrations.MySql.Migrations
.HasColumnName("enabled")
.HasDefaultValueSql("'1'");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("name");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
@ -88,7 +113,7 @@ namespace ASC.Migrations.MySql.Migrations
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnType("varchar")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
@ -112,10 +137,9 @@ namespace ASC.Migrations.MySql.Migrations
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("route");
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<int>("Status")
.HasColumnType("int")

View File

@ -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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Route")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("route");
b.Property<string>("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<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
@ -112,10 +134,9 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("text")
.HasColumnName("response_payload");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("route");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.Property<int>("Status")
.HasColumnType("int")

View File

@ -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<int>(type: "int", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
route = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false, defaultValueSql: "''"),
method = table.Column<string>(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<int>(type: "int", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
secret_key = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true, defaultValueSql: "''"),
tenant_id = table.Column<int>(type: "int unsigned", nullable: false),
uri = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true, defaultValueSql: "''"),
enabled = table.Column<bool>(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<int>(type: "int", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
config_id = table.Column<int>(type: "int", nullable: false),
creation_time = table.Column<DateTime>(type: "datetime", nullable: false),
webhook_id = table.Column<int>(type: "int", nullable: false),
request_headers = table.Column<string>(type: "json", nullable: true),
request_payload = table.Column<string>(type: "text", nullable: false),
response_headers = table.Column<string>(type: "json", nullable: true),
response_payload = table.Column<string>(type: "text", nullable: true),
status = table.Column<int>(type: "int", nullable: false),
tenant_id = table.Column<int>(type: "int unsigned", nullable: false),
uid = table.Column<string>(type: "varchar", maxLength: 50, nullable: false),
delivery = table.Column<DateTime>(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<int>(type: "int", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
secret_key = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true, defaultValueSql: "''"),
tenant_id = table.Column<int>(type: "int unsigned", nullable: false),
uri = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true, defaultValueSql: "''"),
enabled = table.Column<bool>(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<int>(type: "int", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
config_id = table.Column<int>(type: "int", nullable: false),
creation_time = table.Column<DateTime>(type: "datetime", nullable: false),
method = table.Column<string>(type: "varchar", maxLength: 100, nullable: true),
route = table.Column<string>(type: "varchar", maxLength: 100, nullable: true),
request_headers = table.Column<string>(type: "json", nullable: true),
request_payload = table.Column<string>(type: "text", nullable: false),
response_headers = table.Column<string>(type: "json", nullable: true),
response_payload = table.Column<string>(type: "text", nullable: true),
status = table.Column<int>(type: "int", nullable: false),
tenant_id = table.Column<int>(type: "int unsigned", nullable: false),
uid = table.Column<string>(type: "varchar", maxLength: 50, nullable: false),
delivery = table.Column<DateTime>(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");
}
}

View File

@ -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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Method")
.ValueGeneratedOnAdd()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("method")
.HasDefaultValueSql("''");
b.Property<string>("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<int>("Id")
@ -35,12 +63,6 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnName("enabled")
.HasDefaultValueSql("true");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
@ -80,6 +102,10 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime")
.HasColumnName("creation_time");
@ -88,11 +114,6 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
@ -110,11 +131,6 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("text")
.HasColumnName("response_payload");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("route");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");

View File

@ -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"
}

View File

@ -1,10 +1,14 @@
{
"EmailErrorMessage": "Адрес электронной почты недействителен. Вы можете отредактировать адрес, нажав на него.",
"IndividualInvitation": "Индивидуальное приглашение",
"InviteAccountSearchPlaceholder": "Пригласить людей по электронной почте",
"InviteRoomSearchPlaceholder": "Приглашайте людей по имени или электронной почте",
"InviteUsersToRoom": "Пригласить пользователей в комнату",
"Invited": "Приглашен",
"LinkCopySuccess": "Ссылка скопирована",
"SendInvitation": "Выслать приглашение"
"SendInvitation": "Выслать приглашение",
"InviteViaLink": "Пригласить по ссылке",
"InviteViaLinkDescriptionRoom": "Создать универсальную ссылку для самостоятельной авторизации в комнате",
"InviteViaLinkDescriptionAccounts": "Создать универсальную ссылку для самостоятельной авторизации в DocSpace",
"AddManually": "Добавить вручную",
"AddManuallyDescriptionRoom": "Добавьте существующих пользователей DocSpace в комнату используя имена или лично пригласите новых пользователей по электронной почте",
"AddManuallyDescriptionAccounts": "Приглашайте новых пользователей в DocSpace лично по электронной почте"
}

View File

@ -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,
};

View File

@ -199,9 +199,7 @@ const InvitePanel = ({
zIndex={310}
>
<StyledBlock>
<StyledHeading>
{roomId === -1 ? t("Common:InviteUsers") : t("InviteUsersToRoom")}
</StyledHeading>
<StyledHeading>{t("Common:InviteUsers")}</StyledHeading>
</StyledBlock>
<ExternalLinks

View File

@ -20,6 +20,7 @@ import {
StyledInviteInput,
StyledInviteInputContainer,
StyledToggleButton,
StyledDescription,
} from "../StyledInvitePanel";
const ExternalLinks = ({
@ -156,7 +157,7 @@ const ExternalLinks = ({
return (
<StyledBlock noPadding ref={inputsRef}>
<StyledSubHeader inline>
{t("SharingPanel:ExternalLink")}
{t("InviteViaLink")}
{false && ( //TODO: Change to linksVisible after added link information from backend
<div style={{ position: "relative" }}>
<IconButton
@ -186,6 +187,11 @@ const ExternalLinks = ({
)}
<StyledToggleButton isChecked={linksVisible} onChange={toggleLinks} />
</StyledSubHeader>
<StyledDescription>
{roomId === -1
? t("InviteViaLinkDescriptionAccounts")
: t("InviteViaLinkDescriptionRoom")}
</StyledDescription>
{linksVisible && (
<StyledInviteInputContainer key={activeLink.id}>
<StyledInviteInput>

View File

@ -21,6 +21,7 @@ import {
StyledInviteInputContainer,
StyledDropDown,
SearchItemText,
StyledDescription,
} from "../StyledInvitePanel";
const InviteInput = ({
@ -223,7 +224,7 @@ const InviteInput = ({
return (
<>
<StyledSubHeader>
{t("IndividualInvitation")}
{t("AddManually")}
{!hideSelector && (
<StyledLink
fontWeight="600"
@ -235,6 +236,11 @@ const InviteInput = ({
</StyledLink>
)}
</StyledSubHeader>
<StyledDescription>
{roomId === -1
? t("AddManuallyDescriptionAccounts")
: t("AddManuallyDescriptionRoom")}
</StyledDescription>
<StyledInviteInputContainer ref={inputsRef}>
<StyledInviteInput ref={searchRef}>

View File

@ -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 {

View File

@ -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 };

View File

@ -31,7 +31,7 @@ const SeveralItems = ({ isAccounts, theme, selectedItems }) => {
isAccounts={isAccounts}
className="no-thumbnail-img-wrapper"
>
<img size="96px" src={imgSrc} />
<img src={imgSrc} />
<Text fontSize="16px" fontWeight={700}>
{`${itemsText}: ${selectedItems.length}`}
</Text>

View File

@ -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 (
<TableHeader
isLengthenHeader={firstElemChecked || isHeaderChecked}
@ -399,8 +405,8 @@ class FilesTableHeader extends React.Component {
sortBy={sortBy}
containerRef={containerRef}
columns={columns}
columnStorageName={currentColumnStorageName}
columnInfoPanelStorageName={currentColumnInfoPanelStorageName}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
sectionWidth={sectionWidth}
resetColumnsSize={resetColumnsSize}
sortingVisible={sortingVisible}
@ -443,10 +449,6 @@ export default inject(
tableStorageName,
columnStorageName,
columnInfoPanelStorageName,
filesColumnStorageName,
roomsColumnStorageName,
filesColumnInfoPanelStorageName,
roomsColumnInfoPanelStorageName,
nameColumnIsEnabled,
authorColumnIsEnabled,
@ -494,10 +496,6 @@ export default inject(
tableStorageName,
columnStorageName,
columnInfoPanelStorageName,
filesColumnStorageName,
roomsColumnStorageName,
filesColumnInfoPanelStorageName,
roomsColumnInfoPanelStorageName,
nameColumnIsEnabled,
authorColumnIsEnabled,

View File

@ -26,10 +26,12 @@ const RoomCell = ({ sideColor, item }) => {
setIsTooltipLoading(false);
};
const canVisibleTitle = originRoomTitle || originTitle;
return [
<StyledText
key="cell"
fontSize="12px"
fontSize={canVisibleTitle ? "12px" : "13px"}
fontWeight={600}
color={sideColor}
className="row_update-text"
@ -38,7 +40,7 @@ const RoomCell = ({ sideColor, item }) => {
data-tip={""}
data-place={"bottom"}
>
{originRoomTitle || originTitle}
{originRoomTitle || originTitle || "—"}
</StyledText>,
<Tooltip

View File

@ -924,7 +924,7 @@ const SectionFilterContent = ({
filterOptions.push(...thirdPartyOptions);
}
} else {
if (!isRecentFolder && !isFavoritesFolder) {
if (!isRecentFolder && !isFavoritesFolder && !isTrash) {
const foldersOptions = [
{
key: FilterGroups.filterFolders,

View File

@ -70,7 +70,9 @@ const MainProfile = (props) => {
<Text as="div" color="#A3A9AE" className="label">
{t("Common:Name")}
</Text>
<Text fontWeight={600}>{profile.displayName}</Text>
<Text fontWeight={600} truncate>
{profile.displayName}
</Text>
</div>
<IconButton
className="edit-button"

View File

@ -54,6 +54,8 @@ export const StyledInfo = styled.div`
flex-direction: column;
gap: 16px;
max-width: 100%;
@media ${hugeMobile} {
gap: 8px;
}
@ -64,6 +66,8 @@ export const StyledInfo = styled.div`
align-items: center;
gap: 8px;
max-width: 100%;
@media ${desktop} {
height: 20px;
}
@ -72,6 +76,8 @@ export const StyledInfo = styled.div`
display: flex;
gap: 16px;
max-width: calc(100% - 28px);
& > p {
padding-left: 8px;
}

View File

@ -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;
};

View File

@ -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
}

View File

@ -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,

View File

@ -105,6 +105,7 @@ const FilterBlockItem = ({
className="filter-text"
noSelect={true}
isSelected={item.isSelected}
truncate
>
{item?.selectedLabel?.toLowerCase()}
</StyledFilterBlockItemTagText>

View File

@ -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<T>();
var newTags = new List<Tag>();
var updateTags = new List<Tag>();
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<FileEntry<int>>().ToList());
await GetNewTagsAsync(userID, entries.OfType<FileEntry<string>>().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<FileEntry<int>>().ToList());
await GetNewTagsAsync(userID, entries.OfType<FileEntry<string>>().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));

View File

@ -41,7 +41,7 @@ public abstract class ApiControllerBase : ControllerBase
_fileDtoHelper = fileDtoHelper;
}
public async Task<FileEntryDto> GetFileEntryWrapperAsync(FileEntry r)
protected async Task<FileEntryDto> GetFileEntryWrapperAsync(FileEntry r)
{
FileEntryDto wrapper = null;
if (r.FileEntryType == FileEntryType.Folder)

View File

@ -32,18 +32,24 @@ public class SettingsController : ApiControllerBase
{
private readonly FileStorageService<string> _fileStorageServiceString;
private readonly FilesSettingsHelper _filesSettingsHelper;
private readonly ProductEntryPoint _productEntryPoint;
private readonly ProductEntryPoint _productEntryPoint;
private readonly SettingsManager _settingsManager;
private readonly TenantManager _tenantManager;
public SettingsController(
FileStorageService<string> 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;
}
/// <summary>

View File

@ -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)

View File

@ -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)

View File

@ -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);

View File

@ -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);
}

View File

@ -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<WebhooksConfig, WebhooksConfigDto>(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<WebhooksConfig, WebhooksConfigDto>(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<WebhooksLog, WebhooksLogDto>(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<WebhooksLog, WebhooksLogDto>(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<WebhooksLog, WebhooksLogDto>(result);
}
}
}
[HttpGet("webhook/ssl")]
public WebhooksSslSettingsDto GetSslSettings()
{
_permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
var settings = _settingsManager.Load<WebHooksSettings>();
return _mapper.Map<WebhooksSslSettingsDto>(settings);
}
[HttpPost("webhook/ssl/{isEnabled}")]
public WebhooksSslSettingsDto SetSslSettings(bool isEnabled)
{
_permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
var settings = _settingsManager.Load<WebHooksSettings>();
settings.EnableSSLVerification = isEnabled;
_settingsManager.Save(settings);
return _mapper.Map<WebhooksSslSettingsDto>(settings);
}
[HttpGet("webhooks")]
public async IAsyncEnumerable<Webhook> Settings()
{
var settings = _settingsManager.Load<WebHooksSettings>();
foreach (var w in await _webhookDbWorker.GetWebhooksAsync())
{
w.Disable = settings.Ids.Contains(w.Id);
yield return w;
}
}
[HttpPut("webhook/{id}")]
public async Task<Webhook> DisableWebHook(int id)
{
var settings = _settingsManager.Load<WebHooksSettings>();
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;
}
}

View File

@ -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; }

View File

@ -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; }
}

View File

@ -29,7 +29,6 @@ namespace ASC.Web.Api.ApiModels.ResponseDto;
public class WebhooksConfigDto : IMapFrom<WebhooksConfig>
{
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; }

View File

@ -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<WebHooksSettings>
{
public bool IsEnabled { get; set; }
}

View File

@ -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)

View File

@ -13,8 +13,13 @@
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\ASC.Api.Core\ASC.Api.Core.csproj" />
<ProjectReference Include="..\..\common\ASC.Webhooks.Core\ASC.Webhooks.Core.csproj" />
<ProjectReference Include="..\ASC.Web.Core\ASC.Web.Core.csproj" />
</ItemGroup>

View File

@ -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;

View File

@ -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>(containerBuilder =>
{
startup.ConfigureContainer(containerBuilder);
});
builder.Host.ConfigureContainer<ContainerBuilder>(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(".", "");
}

View File

@ -76,5 +76,37 @@ public class Startup : BaseStartup
DIHelper.TryAdd<FacebookLoginProvider>();
DIHelper.TryAdd<LinkedInLoginProvider>();
DIHelper.TryAdd<SsoHandlerService>();
services.AddHttpClient();
DIHelper.TryAdd<DbWorker>();
services.AddHostedService<WorkerService>();
DIHelper.TryAdd<WorkerService>();
services.AddHttpClient("webhook")
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddPolicyHandler((s, request) =>
{
var settings = s.GetRequiredService<Settings>();
return HttpPolicyExtensions
.HandleTransientHttpError()
.Or<SslException>()
.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<SslHelper>();
return helper.ValidateCertificate(sslPolicyErrors);
}
};
});
}
}

View File

@ -29,4 +29,7 @@ internal static partial class WebhookSenderLogger
{
[LoggerMessage(Level = LogLevel.Debug, Message = "Procedure: Finish.")]
public static partial void DebugProcedureFinish(this ILogger<WorkerService> logger);
[LoggerMessage(Level = LogLevel.Error)]
public static partial void ErrorSSLVerification(this ILogger logger, Exception exception);
}

View File

@ -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<DbWorker>();
services.AddHostedService<WorkerService>();
DIHelper.TryAdd<WorkerService>();
services.AddHttpClient("webhook")
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddPolicyHandler((s, request) =>
if (sslPolicyErrors == SslPolicyErrors.None)
{
var settings = s.GetRequiredService<Settings>();
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<WebHooksSettings>();
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;
}
}

View File

@ -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);
}
}