diff --git a/build/Jenkinsfile b/build/Jenkinsfile index 056928b739..f6a9021f08 100644 --- a/build/Jenkinsfile +++ b/build/Jenkinsfile @@ -43,20 +43,20 @@ pipeline { stages { stage('Components') { steps { - sh "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}/packages/asc-web-components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true" + sh "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}/packages/components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true" } post { success { - junit 'packages/asc-web-components/junit.xml' + junit 'packages/components/junit.xml' publishHTML target: [ allowMissing : false, alwaysLinkToLastBuild: false, keepAll : true, - reportDir : 'packages/asc-web-components/coverage/lcov-report', + reportDir : 'packages/components/coverage/lcov-report', reportFiles : 'index.html', reportName : 'Unix Test Report' ] - publishCoverage adapters: [coberturaAdapter('packages/asc-web-components/coverage/cobertura-coverage.xml')] + publishCoverage adapters: [coberturaAdapter('packages/components/coverage/cobertura-coverage.xml')] } } } @@ -72,16 +72,16 @@ pipeline { stages { stage('Components') { steps { - bat "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}\\packages\\asc-web-components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true" + bat "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}\\packages\\components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true" } post { success { - junit 'packages\\asc-web-components\\junit.xml' + junit 'packages\\components\\junit.xml' publishHTML target: [ allowMissing : false, alwaysLinkToLastBuild: false, keepAll : true, - reportDir : 'packages\\asc-web-components\\coverage\\lcov-report', + reportDir : 'packages\\components\\coverage\\lcov-report', reportFiles : 'index.html', reportName : 'Windows Test Report' ] diff --git a/build/build.backend.bat b/build/build.backend.bat index 36c58d38e5..a4b3df95b7 100644 --- a/build/build.backend.bat +++ b/build/build.backend.bat @@ -10,26 +10,31 @@ if %errorlevel% == 0 ( call start\stop.bat nopause dotnet build ..\asc.web.slnf /fl1 /flp1:logfile=asc.web.log;verbosity=normal echo. +) + +if %errorlevel% == 0 ( echo install nodejs projects dependencies... echo. - for /R "scripts\" %%f in (*.bat) do ( echo Run script %%~nxf... echo. call scripts\%%~nxf ) +) echo. +if %errorlevel% == 0 ( call start\start.bat nopause +) echo. if "%1"=="nopause" goto end pause -) + :end \ No newline at end of file diff --git a/build/createMigrations.bat b/build/createMigrations.bat index e5726af891..1f7fcf9ca0 100644 --- a/build/createMigrations.bat +++ b/build/createMigrations.bat @@ -1,5 +1,6 @@ @echo "MIGRATIONS" @echo off -PUSHD %~dp0..\common\Tools\Migration.Creator -dotnet run --project Migration.Creator.csproj \ No newline at end of file +PUSHD %~dp0..\common\Tools\ASC.Migration.Creator +dotnet run --project ASC.Migration.Creator.csproj +pause \ No newline at end of file diff --git a/build/install/install.bat b/build/install/install.bat index 321fb88d69..c78c8e8745 100644 --- a/build/install/install.bat +++ b/build/install/install.bat @@ -1,9 +1,11 @@ @echo off -PUSHD %~dp0..\.. -setlocal EnableDelayedExpansion +PUSHD %~dp0.. +call runasadmin.bat "%~dpnx0" if %errorlevel% == 0 ( + PUSHD %~dp0..\.. + setlocal EnableDelayedExpansion for /R "build\run\" %%f in (*.bat) do ( call build\run\%%~nxf echo service create "Onlyoffice%%~nf" diff --git a/common/ASC.Api.Core/ASC.Api.Core.csproj b/common/ASC.Api.Core/ASC.Api.Core.csproj index 482a686f5f..e9697161c6 100644 --- a/common/ASC.Api.Core/ASC.Api.Core.csproj +++ b/common/ASC.Api.Core/ASC.Api.Core.csproj @@ -18,6 +18,7 @@ + diff --git a/common/ASC.Api.Core/Auth/BasicAuthHandler.cs b/common/ASC.Api.Core/Auth/BasicAuthHandler.cs new file mode 100644 index 0000000000..7693eb272c --- /dev/null +++ b/common/ASC.Api.Core/Auth/BasicAuthHandler.cs @@ -0,0 +1,103 @@ +// (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 System.Text; +using System.Text.RegularExpressions; + +using SecurityContext = ASC.Core.SecurityContext; + +namespace ASC.Api.Core.Auth; + +[Scope] +public class BasicAuthHandler : AuthenticationHandler +{ + private readonly UserManager _userManager; + private readonly SecurityContext _securityContext; + private readonly PasswordHasher _passwordHasher; + + public BasicAuthHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock + ) : base(options, logger, encoder, clock) + { + + } + + public BasicAuthHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, + UserManager userManager, + SecurityContext securityContext, + PasswordHasher passwordHasher) : this(options, logger, encoder, clock) + { + _userManager = userManager; + _securityContext = securityContext; + _passwordHasher = passwordHasher; + } + + protected override Task HandleAuthenticateAsync() + { + Response.Headers.Add("WWW-Authenticate", "Basic"); + + if (!Request.Headers.ContainsKey("Authorization")) + { + return Task.FromResult(AuthenticateResult.Fail("Authorization header missing.")); + } + + // Get authorization key + var authorizationHeader = Request.Headers["Authorization"].ToString(); + var authHeaderRegex = new Regex(@"Basic (.*)"); + + if (!authHeaderRegex.IsMatch(authorizationHeader)) + { + return Task.FromResult(AuthenticateResult.Fail("Authorization code not formatted properly.")); + } + + var authBase64 = Encoding.UTF8.GetString(Convert.FromBase64String(authHeaderRegex.Replace(authorizationHeader, "$1"))); + var authSplit = authBase64.Split(Convert.ToChar(":"), 2); + var authUsername = authSplit[0]; + var authPassword = authSplit.Length > 1 ? authSplit[1] : throw new Exception("Unable to get password"); + + try + { + var userInfo = _userManager.GetUserByEmail(authUsername); + var passwordHash = _passwordHasher.GetClientPassword(authPassword); + + _securityContext.AuthenticateMe(userInfo.Email, passwordHash); + + } + catch (Exception) + { + return Task.FromResult(AuthenticateResult.Fail("The username or password is not correct.")); + } + + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, Scheme.Name))); + } +} diff --git a/common/ASC.Api.Core/Auth/CookieAuthHandler.cs b/common/ASC.Api.Core/Auth/CookieAuthHandler.cs index 5cc88ad434..33b81fdf1b 100644 --- a/common/ASC.Api.Core/Auth/CookieAuthHandler.cs +++ b/common/ASC.Api.Core/Auth/CookieAuthHandler.cs @@ -31,9 +31,9 @@ namespace ASC.Api.Core.Auth; [Scope] public class CookieAuthHandler : AuthenticationHandler { - private readonly AuthorizationHelper _authorizationHelper; private readonly SecurityContext _securityContext; private readonly CookiesManager _cookiesManager; + private readonly IHttpContextAccessor _httpContextAccessor; public CookieAuthHandler( IOptionsMonitor options, @@ -42,31 +42,59 @@ public class CookieAuthHandler : AuthenticationHandler options, - ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, - AuthorizationHelper authorizationHelper, + public CookieAuthHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, SecurityContext securityContext, - CookiesManager cookiesManager) + CookiesManager cookiesManager, + IHttpContextAccessor httpContextAccessor) : this(options, logger, encoder, clock) { - _authorizationHelper = authorizationHelper; _securityContext = securityContext; _cookiesManager = cookiesManager; + _httpContextAccessor = httpContextAccessor; } protected override Task HandleAuthenticateAsync() { - var result = _authorizationHelper.ProcessBasicAuthorization(out _); - if (!result) + try { - _securityContext.Logout(); - _cookiesManager.ClearCookies(CookiesType.AuthKey); - _cookiesManager.ClearCookies(CookiesType.SocketIO); + var authorization = _httpContextAccessor.HttpContext.Request.Cookies["asc_auth_key"] ?? _httpContextAccessor.HttpContext.Request.Headers["Authorization"].ToString(); + + if (string.IsNullOrEmpty(authorization)) + { + throw new AuthenticationException(nameof(HttpStatusCode.Unauthorized)); + } + + authorization = authorization.Trim(); + + if (0 <= authorization.IndexOf("Bearer", 0)) + { + authorization = authorization.Substring("Bearer ".Length); + } + + if (!_securityContext.AuthenticateMe(authorization)) + { + throw new AuthenticationException(nameof(HttpStatusCode.Unauthorized)); + } + + } + catch (Exception) + { + return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(nameof(HttpStatusCode.Unauthorized)))); + } + finally + { + if (!_securityContext.IsAuthenticated) + { + _securityContext.Logout(); + _cookiesManager.ClearCookies(CookiesType.AuthKey); + _cookiesManager.ClearCookies(CookiesType.SocketIO); + } } - return Task.FromResult( - result ? - AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)) : - AuthenticateResult.Fail(new AuthenticationException(nameof(HttpStatusCode.Unauthorized)))); + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, Scheme.Name))); } } \ No newline at end of file diff --git a/common/ASC.Api.Core/Core/BaseStartup.cs b/common/ASC.Api.Core/Core/BaseStartup.cs index 1b747cdc60..276446c7fe 100644 --- a/common/ASC.Api.Core/Core/BaseStartup.cs +++ b/common/ASC.Api.Core/Core/BaseStartup.cs @@ -24,6 +24,11 @@ // 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.IdentityModel.Tokens.Jwt; + +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Net.Http.Headers; + using JsonConverter = System.Text.Json.Serialization.JsonConverter; namespace ASC.Api.Core; @@ -65,6 +70,7 @@ public abstract class BaseStartup services.AddBaseDbContextPool(); services.AddBaseDbContextPool(); services.AddBaseDbContextPool(); + services.AddBaseDbContextPool(); services.AddBaseDbContextPool(); services.AddBaseDbContextPool(); services.AddBaseDbContextPool(); @@ -102,13 +108,13 @@ public abstract class BaseStartup services.AddSingleton(jsonOptions); DIHelper.AddControllers(); - DIHelper.TryAdd(); DIHelper.TryAdd(); DIHelper.TryAdd(); DIHelper.TryAdd(); DIHelper.TryAdd(); DIHelper.TryAdd(); DIHelper.TryAdd(); + DIHelper.TryAdd(); DIHelper.TryAdd(); DIHelper.TryAdd(); @@ -147,16 +153,72 @@ public abstract class BaseStartup config.OutputFormatters.Add(new XmlOutputFormatter()); }); - var authBuilder = services.AddAuthentication("cookie") - .AddScheme("cookie", a => { }); - - if (ConfirmAddScheme) + var authBuilder = services.AddAuthentication(options => { - authBuilder.AddScheme("confirm", a => { }); - } + options.DefaultScheme = "MultiAuthSchemes"; + options.DefaultChallengeScheme = "MultiAuthSchemes"; + }).AddScheme(CookieAuthenticationDefaults.AuthenticationScheme, a => { }) + .AddScheme("Basic", a => { }) + .AddScheme("confirm", a => { }) + .AddJwtBearer("Bearer", options => + { + options.Authority = _configuration["core:oidc:authority"]; + options.IncludeErrorDetails = true; + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false + }; + + options.Events = new JwtBearerEvents + { + OnTokenValidated = ctx => + { + using var scope = ctx.HttpContext.RequestServices.CreateScope(); + + var securityContext = scope.ServiceProvider.GetService(); + + var claimUserId = ctx.Principal.FindFirstValue("userId"); + + if (String.IsNullOrEmpty(claimUserId)) + { + throw new Exception("Claim 'UserId' is not present in claim list"); + } + + var userId = new Guid(claimUserId); + + securityContext.AuthenticateMeWithoutCookie(userId, ctx.Principal.Claims.ToList()); + + return Task.CompletedTask; + } + }; + }) + .AddPolicyScheme("MultiAuthSchemes", JwtBearerDefaults.AuthenticationScheme, options => + { + + options.ForwardDefaultSelector = context => + { + var authorizationHeader = context.Request.Headers[HeaderNames.Authorization].FirstOrDefault(); + + if (String.IsNullOrEmpty(authorizationHeader)) return CookieAuthenticationDefaults.AuthenticationScheme; + + if (authorizationHeader.StartsWith("Basic ")) return "Basic"; + + if (authorizationHeader.StartsWith("Bearer ")) + { + var token = authorizationHeader.Substring("Bearer ".Length).Trim(); + var jwtHandler = new JwtSecurityTokenHandler(); + + return (jwtHandler.CanReadToken(token) && jwtHandler.ReadJwtToken(token).Issuer.Equals(_configuration["core:oidc:authority"])) + ? JwtBearerDefaults.AuthenticationScheme : CookieAuthenticationDefaults.AuthenticationScheme; + } + + return CookieAuthenticationDefaults.AuthenticationScheme; + }; + }); services.AddAutoMapper(GetAutoMapperProfileAssemblies()); - + if (!_hostEnvironment.IsDevelopment()) { services.AddStartupTask() @@ -189,8 +251,6 @@ public abstract class BaseStartup app.UseCultureMiddleware(); - app.UseDisposeMiddleware(); - app.UseEndpoints(endpoints => { endpoints.MapCustom(); diff --git a/common/ASC.Api.Core/GlobalUsings.cs b/common/ASC.Api.Core/GlobalUsings.cs index bb8f3e803d..298e86ee55 100644 --- a/common/ASC.Api.Core/GlobalUsings.cs +++ b/common/ASC.Api.Core/GlobalUsings.cs @@ -24,8 +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 -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using System.Collections.Concurrent; global using System.ComponentModel; global using System.Globalization; global using System.Linq.Expressions; @@ -36,6 +34,7 @@ global using System.Runtime.Serialization; global using System.Security; global using System.Security.Authentication; global using System.Security.Claims; +global using System.Text; global using System.Text.Encodings.Web; global using System.Text.Json; global using System.Text.Json.Serialization; @@ -56,8 +55,8 @@ global using ASC.AuditTrail.Types; global using ASC.Common; global using ASC.Common.Caching; global using ASC.Common.DependencyInjection; +global using ASC.Common.Log; global using ASC.Common.Logging; -global using ASC.Common.Mapping; global using ASC.Common.Notify.Engine; global using ASC.Common.Threading; global using ASC.Common.Utils; @@ -119,11 +118,14 @@ global using Microsoft.AspNetCore.WebUtilities; global using Microsoft.EntityFrameworkCore; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Diagnostics.HealthChecks; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Primitives; +global using Microsoft.AspNetCore.Authentication.JwtBearer; +global using Microsoft.IdentityModel.Tokens; global using Newtonsoft.Json; global using Newtonsoft.Json.Serialization; diff --git a/common/ASC.Api.Core/Middleware/CommonApiResponse.cs b/common/ASC.Api.Core/Middleware/CommonApiResponse.cs index e3ac097a8a..d0de60efc8 100644 --- a/common/ASC.Api.Core/Middleware/CommonApiResponse.cs +++ b/common/ASC.Api.Core/Middleware/CommonApiResponse.cs @@ -56,44 +56,64 @@ public class ErrorApiResponse : CommonApiResponse public class SuccessApiResponse : CommonApiResponse { - public int? Count { get; set; } - public long? Total { get; set; } + private readonly HttpContext _httpContext; + public object Response { get; set; } + public int? Count + { + get + { + + if (_httpContext.Items.TryGetValue("Count", out var count)) + { + return (int?)count; + } + + if (Response is List list) + { + return list.Count; + } + + if (Response is IEnumerable collection) + { + return collection.Count(); + } + + if (Response == null) + { + return 0; + } + else + { + return 1; + } + } + } + + public long? Total + { + get + { + if (_httpContext.Items.TryGetValue("TotalCount", out var total)) + { + return (long?)total; + } + + return null; + } + } + public SuccessApiResponse() { } - protected internal SuccessApiResponse(HttpStatusCode statusCode, object response, long? total = null, int? count = null) : base(statusCode) + protected internal SuccessApiResponse(HttpContext httpContext, object response) : base((HttpStatusCode)httpContext.Response.StatusCode) { Status = 0; + _httpContext = httpContext; Response = response; - Total = total; - - if (count.HasValue) - { - Count = count; - } - else - { - if (response is List list) - { - Count = list.Count; - } - else if (response is IEnumerable collection) - { - Count = collection.Count(); - } - else if (response == null) - { - Count = 0; - } - else - { - Count = 1; - } - } } } diff --git a/common/ASC.Api.Core/Middleware/ProductSecurityFilter.cs b/common/ASC.Api.Core/Middleware/ProductSecurityFilter.cs index 1071234e30..090a732bc2 100644 --- a/common/ASC.Api.Core/Middleware/ProductSecurityFilter.cs +++ b/common/ASC.Api.Core/Middleware/ProductSecurityFilter.cs @@ -24,6 +24,8 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +using CallContext = ASC.Common.Notify.Engine.CallContext; + namespace ASC.Api.Core.Middleware; [Scope] diff --git a/common/ASC.Api.Core/Middleware/ResponseWrapper.cs b/common/ASC.Api.Core/Middleware/ResponseWrapper.cs index c42b593426..8eebf6b0f1 100644 --- a/common/ASC.Api.Core/Middleware/ResponseWrapper.cs +++ b/common/ASC.Api.Core/Middleware/ResponseWrapper.cs @@ -77,9 +77,7 @@ public class CustomResponseFilterAttribute : ResultFilterAttribute { if (context.Result is ObjectResult result) { - context.HttpContext.Items.TryGetValue("TotalCount", out var total); - context.HttpContext.Items.TryGetValue("Count", out var count); - result.Value = new SuccessApiResponse((HttpStatusCode)context.HttpContext.Response.StatusCode, result.Value, (long?)total, (int?)count); + result.Value = new SuccessApiResponse(context.HttpContext, result.Value); } base.OnResultExecuting(context); diff --git a/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs b/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs index e46dce71a5..6b03a2b28d 100644 --- a/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs +++ b/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs @@ -24,8 +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 System.Text; - namespace ASC.Api.Core.Middleware; [Scope] @@ -34,12 +32,14 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable private readonly MemoryStream _stream; private Stream _bodyStream; private readonly IWebhookPublisher _webhookPublisher; + private readonly ILogger _logger; private static readonly List _methodList = new List { "POST", "UPDATE", "DELETE" }; - public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher) + public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher, ILogger logger) { _stream = new MemoryStream(); _webhookPublisher = webhookPublisher; + _logger = logger; } public override void OnResultExecuting(ResultExecutingContext context) @@ -68,11 +68,18 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable await _stream.CopyToAsync(_bodyStream); context.HttpContext.Response.Body = _bodyStream; - var (method, routePattern) = GetData(context.HttpContext); + try + { + var (method, routePattern) = GetData(context.HttpContext); - var resultContent = Encoding.UTF8.GetString(_stream.ToArray()); - var eventName = $"method: {method}, route: {routePattern}"; - _webhookPublisher.Publish(eventName, resultContent); + var resultContent = Encoding.UTF8.GetString(_stream.ToArray()); + + await _webhookPublisher.PublishAsync(method, routePattern, resultContent); + } + catch (Exception e) + { + _logger.ErrorWithException(e); + } } } diff --git a/common/ASC.Common/Threading/DistributedTask.cs b/common/ASC.Common/Threading/DistributedTask.cs index d5c96529d0..ff3ab2caa7 100644 --- a/common/ASC.Common/Threading/DistributedTask.cs +++ b/common/ASC.Common/Threading/DistributedTask.cs @@ -77,7 +77,6 @@ public class DistributedTask Publication(this); } - [Obsolete("GetProperty is deprecated, please use indexer this[propName] instead.")] public T GetProperty(string propName) { if (!_props.TryGetValue(propName, out var propValue)) @@ -88,8 +87,7 @@ public class DistributedTask return JsonSerializer.Deserialize(propValue); } - [Obsolete("SetProperty is deprecated, please use indexer this[propName] = propValue instead.")] - public void SetProperty(string propName, object propValue) + public void SetProperty(string propName, T propValue) { _props[propName] = JsonSerializer.Serialize(propValue); } diff --git a/common/ASC.Common/Web/DisposableHttpContext.cs b/common/ASC.Common/Web/DisposableHttpContext.cs deleted file mode 100644 index 8659d5def0..0000000000 --- a/common/ASC.Common/Web/DisposableHttpContext.cs +++ /dev/null @@ -1,93 +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 - -namespace ASC.Common.Web; - -public class DisposableHttpContext : IDisposable -{ - private const string Key = "disposable.key"; - - public object this[string key] - { - get => Items.ContainsKey(key) ? Items[key] : null; - set - { - if (value == null) - { - throw new ArgumentNullException(); - } - - if (value is not IDisposable) - { - throw new ArgumentException("Only IDisposable may be added!"); - } - - Items[key] = (IDisposable)value; - } - } - - private Dictionary Items - { - get - { - var table = (Dictionary)_context.Items[Key]; - - if (table == null) - { - table = new Dictionary(1); - _context.Items.Add(Key, table); - } - - return table; - } - } - - private readonly HttpContext _context; - private bool _isDisposed; - - public DisposableHttpContext(HttpContext ctx) - { - ArgumentNullException.ThrowIfNull(ctx); - _context = ctx; - } - - public void Dispose() - { - if (!_isDisposed) - { - foreach (var item in Items.Values) - { - try - { - item.Dispose(); - } - catch { } - } - - _isDisposed = true; - } - } -} \ No newline at end of file diff --git a/common/ASC.Core.Common/ASC.Core.Common.csproj b/common/ASC.Core.Common/ASC.Core.Common.csproj index 7f771c314d..ff6a6ca53f 100644 --- a/common/ASC.Core.Common/ASC.Core.Common.csproj +++ b/common/ASC.Core.Common/ASC.Core.Common.csproj @@ -50,6 +50,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/common/ASC.Core.Common/Context/SecurityContext.cs b/common/ASC.Core.Common/Context/SecurityContext.cs index 2d012e5c46..cc057658a0 100644 --- a/common/ASC.Core.Common/Context/SecurityContext.cs +++ b/common/ASC.Core.Common/Context/SecurityContext.cs @@ -97,9 +97,10 @@ public class SecurityContext public bool AuthenticateMe(string cookie) { - if (!string.IsNullOrEmpty(cookie)) - { + if (string.IsNullOrEmpty(cookie)) return false; + if (!_cookieStorage.DecryptCookie(cookie, out var tenant, out var userid, out var indexTenant, out var expire, out var indexUser, out var loginEventId)) + { if (cookie.Equals("Bearer", StringComparison.InvariantCulture)) { var ipFrom = string.Empty; @@ -115,54 +116,6 @@ public class SecurityContext } _logger.InformationEmptyBearer(ipFrom, address); } - else if (_cookieStorage.DecryptCookie(cookie, out var tenant, out var userid, out var indexTenant, out var expire, out var indexUser, out var loginEventId)) - { - if (tenant != _tenantManager.GetCurrentTenant().Id) - { - return false; - } - - var settingsTenant = _tenantCookieSettingsHelper.GetForTenant(tenant); - if (indexTenant != settingsTenant.Index) - { - return false; - } - - if (expire != DateTime.MaxValue && expire < DateTime.UtcNow) - { - return false; - } - - try - { - var settingsUser = _tenantCookieSettingsHelper.GetForUser(userid); - if (indexUser != settingsUser.Index) - { - return false; - } - - var settingLoginEvents = _dbLoginEventsManager.GetLoginEventIds(tenant, userid).Result; // remove Result - if (loginEventId != 0 && !settingLoginEvents.Contains(loginEventId)) - { - return false; - } - - AuthenticateMeWithoutCookie(new UserAccount(new UserInfo { Id = userid }, tenant, _userFormatter)); - return true; - } - catch (InvalidCredentialException ice) - { - _logger.AuthenticateDebug(cookie, tenant, userid, ice); - } - catch (SecurityException se) - { - _logger.AuthenticateDebug(cookie, tenant, userid, se); - } - catch (Exception err) - { - _logger.AuthenticateError(cookie, tenant, userid, err); - } - } else { var ipFrom = string.Empty; @@ -179,7 +132,57 @@ public class SecurityContext _logger.WarningCanNotDecrypt(cookie, ipFrom, address); } + + return false; } + + if (tenant != _tenantManager.GetCurrentTenant().Id) + { + return false; + } + + var settingsTenant = _tenantCookieSettingsHelper.GetForTenant(tenant); + + if (indexTenant != settingsTenant.Index) + { + return false; + } + + if (expire != DateTime.MaxValue && expire < DateTime.UtcNow) + { + return false; + } + + try + { + var settingsUser = _tenantCookieSettingsHelper.GetForUser(userid); + if (indexUser != settingsUser.Index) + { + return false; + } + + var settingLoginEvents = _dbLoginEventsManager.GetLoginEventIds(tenant, userid).Result; // remove Result + if (loginEventId != 0 && !settingLoginEvents.Contains(loginEventId)) + { + return false; + } + + AuthenticateMeWithoutCookie(new UserAccount(new UserInfo { Id = userid }, tenant, _userFormatter)); + return true; + } + catch (InvalidCredentialException ice) + { + _logger.AuthenticateDebug(cookie, tenant, userid, ice); + } + catch (SecurityException se) + { + _logger.AuthenticateDebug(cookie, tenant, userid, se); + } + catch (Exception err) + { + _logger.AuthenticateError(cookie, tenant, userid, err); + } + return false; } diff --git a/common/ASC.Core.Common/Context/WorkContext.cs b/common/ASC.Core.Common/Context/WorkContext.cs index 8f0c6024db..f54d5d77a8 100644 --- a/common/ASC.Core.Common/Context/WorkContext.cs +++ b/common/ASC.Core.Common/Context/WorkContext.cs @@ -41,6 +41,7 @@ public class WorkContext private readonly SmtpSender _smtpSender; private readonly NotifyServiceSender _notifyServiceSender; private readonly TelegramSender _telegramSender; + private readonly PushSender _pushSender; private static bool _notifyStarted; private static bool? _isMono; private static string _monoVersion; @@ -87,7 +88,8 @@ public class WorkContext AWSSender awsSender, SmtpSender smtpSender, NotifyServiceSender notifyServiceSender, - TelegramSender telegramSender + TelegramSender telegramSender, + PushSender pushSender ) { _serviceProvider = serviceProvider; @@ -100,6 +102,7 @@ public class WorkContext _smtpSender = smtpSender; _notifyServiceSender = notifyServiceSender; _telegramSender = telegramSender; + _pushSender = pushSender; } public void NotifyStartUp() @@ -119,6 +122,8 @@ public class WorkContext INotifySender jabberSender = _notifyServiceSender; INotifySender emailSender = _notifyServiceSender; INotifySender telegramSender = _telegramSender; + INotifySender pushSender = _pushSender; + var postman = _configuration["core:notify:postman"]; @@ -148,6 +153,7 @@ public class WorkContext NotifyContext.RegisterSender(_dispatchEngine, Constants.NotifyEMailSenderSysName, new EmailSenderSink(emailSender, _serviceProvider)); NotifyContext.RegisterSender(_dispatchEngine, Constants.NotifyMessengerSenderSysName, new JabberSenderSink(jabberSender, _serviceProvider)); NotifyContext.RegisterSender(_dispatchEngine, Constants.NotifyTelegramSenderSysName, new TelegramSenderSink(telegramSender, _serviceProvider)); + NotifyContext.RegisterSender(_dispatchEngine, Constants.NotifyPushSenderSysName, new PushSenderSink(pushSender, _serviceProvider)); NotifyEngine.AddAction(); @@ -199,6 +205,7 @@ public static class WorkContextExtension dIHelper.TryAdd(); dIHelper.TryAdd(); dIHelper.TryAdd(); + dIHelper.TryAdd(); dIHelper.TryAdd(); } } diff --git a/common/ASC.Api.Core/Middleware/DisposeMiddleware.cs b/common/ASC.Core.Common/EF/Context/FirebaseDbContext.cs similarity index 71% rename from common/ASC.Api.Core/Middleware/DisposeMiddleware.cs rename to common/ASC.Core.Common/EF/Context/FirebaseDbContext.cs index 3f49d27af6..d4f0af1281 100644 --- a/common/ASC.Api.Core/Middleware/DisposeMiddleware.cs +++ b/common/ASC.Core.Common/EF/Context/FirebaseDbContext.cs @@ -1,52 +1,44 @@ -// (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.Middleware; - -public class DisposeMiddleware -{ - private readonly RequestDelegate _next; - - public DisposeMiddleware(RequestDelegate next) - { - _next = next; - } - - public async Task Invoke(HttpContext context) - { - context.Response.RegisterForDispose(new DisposableHttpContext(context)); - - await _next.Invoke(context); - } -} - -public static class DisposeMiddlewareExtensions -{ - public static IApplicationBuilder UseDisposeMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } -} \ No newline at end of file +// (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.Core.Common.EF.Context; +public class FirebaseDbContext : DbContext +{ + + public DbSet Users { get; set; } + + public FirebaseDbContext(DbContextOptions options) : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + ModelBuilderWrapper + .From(modelBuilder, Database) + .AddFireBaseUsers(); + } +} diff --git a/common/ASC.Core.Common/EF/FireBaseUser.cs b/common/ASC.Core.Common/EF/FireBaseUser.cs new file mode 100644 index 0000000000..a60220bcdd --- /dev/null +++ b/common/ASC.Core.Common/EF/FireBaseUser.cs @@ -0,0 +1,122 @@ +// (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.Core.Common.EF; + +public class FireBaseUser : BaseEntity +{ + public int Id { get; set; } + public Guid UserId { get; set; } + public int TenantId { get; set; } + public string FirebaseDeviceToken { get; set; } + public string Application { get; set; } + public bool? IsSubscribed { get; set; } + + public override object[] GetKeys() + { + return new object[] { Id }; + } +} + +public static class FireBaseUserExtension +{ + public static ModelBuilderWrapper AddFireBaseUsers(this ModelBuilderWrapper modelBuilder) + { + modelBuilder + .Add(MySqlAddFireBaseUsers, Provider.MySql) + .Add(PgSqlAddFireBaseUsers, Provider.PostgreSql); + + return modelBuilder; + } + + public static void MySqlAddFireBaseUsers(this ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.Id }) + .HasName("PRIMARY"); + + entity.ToTable("firebase_users"); + + entity.HasIndex(e => new { e.TenantId, e.UserId }) + .HasDatabaseName("user_id"); + + entity.Property(e => e.Id).HasColumnName("id"); + entity.Property(e => e.TenantId).HasColumnName("tenant_id"); + entity.Property(e => e.IsSubscribed).HasColumnName("is_subscribed"); + + entity.Property(e => e.UserId) + .HasColumnName("user_id") + .HasColumnType("varchar(36)") + .HasCharSet("utf8") + .UseCollation("utf8_general_ci"); + + entity.Property(e => e.FirebaseDeviceToken) + .HasColumnName("firebase_device_token") + .HasColumnType("varchar(255)") + .HasCharSet("utf8") + .UseCollation("utf8_general_ci"); + + entity.Property(e => e.Application) + .HasColumnName("application") + .HasColumnType("varchar(20)") + .HasCharSet("utf8") + .UseCollation("utf8_general_ci"); + + }); + } + + public static void PgSqlAddFireBaseUsers(this ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id) + .HasName("firebase_users_pkey"); + + entity.ToTable("firebase_users", "onlyoffice"); + + entity.HasIndex(e => new { e.TenantId, e.UserId }) + .HasDatabaseName("user_id"); + + entity.Property(e => e.Id).HasColumnName("id"); + entity.Property(e => e.TenantId).HasColumnName("tenant_id"); + entity.Property(e => e.IsSubscribed).HasColumnName("is_subscribed"); + + entity.Property(e => e.UserId) + .HasColumnName("user_id") + .HasMaxLength(36); + + entity.Property(e => e.FirebaseDeviceToken) + .HasColumnName("firebase_device_token") + .HasMaxLength(255); + + entity.Property(e => e.Application) + .HasColumnName("application") + .HasMaxLength(20); + }); + } + +} diff --git a/common/ASC.Core.Common/EF/Model/ModelBuilderWrapper.cs b/common/ASC.Core.Common/EF/Model/ModelBuilderWrapper.cs index e7a21b4270..be538f57e4 100644 --- a/common/ASC.Core.Common/EF/Model/ModelBuilderWrapper.cs +++ b/common/ASC.Core.Common/EF/Model/ModelBuilderWrapper.cs @@ -24,6 +24,8 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +using Microsoft.EntityFrameworkCore.Metadata.Builders; + namespace ASC.Core.Common.EF.Model; public class ModelBuilderWrapper @@ -75,6 +77,11 @@ public class ModelBuilderWrapper return this; } + public EntityTypeBuilder Entity() where T : class + { + return ModelBuilder.Entity(); + } + public void AddDbFunction() { ModelBuilder diff --git a/common/ASC.Core.Common/GlobalUsings.cs b/common/ASC.Core.Common/GlobalUsings.cs index dd807f9244..9f683002af 100644 --- a/common/ASC.Core.Common/GlobalUsings.cs +++ b/common/ASC.Core.Common/GlobalUsings.cs @@ -126,6 +126,8 @@ global using Autofac; global using AutoMapper; global using AutoMapper.QueryableExtensions; +global using Google.Apis.Auth.OAuth2; + global using MailKit.Security; global using Microsoft.AspNetCore.Http; @@ -161,4 +163,7 @@ global using Telegram.Bot; global using static ASC.Security.Cryptography.EmailValidationKeyProvider; -global using JsonIgnoreAttribute = System.Text.Json.Serialization.JsonIgnoreAttribute; \ No newline at end of file +global using JsonIgnoreAttribute = System.Text.Json.Serialization.JsonIgnoreAttribute; +global using FirebaseAdminMessaging = FirebaseAdmin.Messaging; +global using FirebaseApp = FirebaseAdmin.FirebaseApp; +global using AppOptions = FirebaseAdmin.AppOptions; diff --git a/common/ASC.Core.Common/Notify/FirebaseApiKey.cs b/common/ASC.Core.Common/Notify/FirebaseApiKey.cs new file mode 100644 index 0000000000..c83f0c6e93 --- /dev/null +++ b/common/ASC.Core.Common/Notify/FirebaseApiKey.cs @@ -0,0 +1,119 @@ +// (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.Core.Common.Notify; +[Serializable] +class FirebaseApiKey +{ + private readonly IConfiguration _configuration; + + public FirebaseApiKey(IConfiguration configuration) + { + _configuration = configuration; + } + + [JsonProperty("type")] + public string Type + { + get + { + return "service_account"; + } + } + + [JsonProperty("project_id")] + public string ProjectId + { + get + { + return _configuration["firebase:projectId"] ?? ""; + } + } + [JsonProperty("private_key_id")] + public string PrivateKeyId + { + get + { + return _configuration["firebase:privateKeyId"] ?? ""; + } + } + [JsonProperty("private_key")] + public string PrivateKey + { + get + { + return _configuration["firebase:privateKey"] ?? ""; + } + } + [JsonProperty("client_email")] + public string ClientEmail + { + get + { + return _configuration["firebase:clientEmail"] ?? ""; + } + } + [JsonProperty("client_id")] + public string ClientId + { + get + { + return _configuration["firebase:clientId"] ?? ""; + } + } + [JsonProperty("auth_uri")] + public string AuthUri + { + get + { + return "https://accounts.google.com/o/oauth2/auth"; + } + } + [JsonProperty("token_uri")] + public string TokenUri + { + get + { + return "https://oauth2.googleapis.com/token"; + } + } + [JsonProperty("auth_provider_x509_cert_url")] + public string AuthProviderX509CertUrl + { + get + { + return "https://www.googleapis.com/oauth2/v1/certs"; + } + } + [JsonProperty("client_x509_cert_url")] + public string ClientX509CertUrl + { + get + { + return _configuration["firebase:x509CertUrl"] ?? ""; + } + } +} diff --git a/common/ASC.Core.Common/Notify/Messages/NotifyMessage.cs b/common/ASC.Core.Common/Notify/Messages/NotifyMessage.cs index 7dad14c333..ea92e48bf5 100644 --- a/common/ASC.Core.Common/Notify/Messages/NotifyMessage.cs +++ b/common/ASC.Core.Common/Notify/Messages/NotifyMessage.cs @@ -65,6 +65,12 @@ public class NotifyMessage : IMapFrom [ProtoMember(12)] public int TenantId { get; set; } + [ProtoMember(13)] + public string ProductID { get; set; } + + [ProtoMember(14)] + public string Data { get; set; } + public void Mapping(Profile profile) { profile.CreateMap() diff --git a/common/ASC.Core.Common/Notify/Push/Dao/FirebaseDao.cs b/common/ASC.Core.Common/Notify/Push/Dao/FirebaseDao.cs new file mode 100644 index 0000000000..cae0eddc10 --- /dev/null +++ b/common/ASC.Core.Common/Notify/Push/Dao/FirebaseDao.cs @@ -0,0 +1,99 @@ +// (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.Core.Common.Notify.Push; + +[Scope] +public class FirebaseDao +{ + private readonly IDbContextFactory _dbContextFactory; + + public FirebaseDao(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + public FireBaseUser RegisterUserDevice(Guid userId, int tenantId, string fbDeviceToken, bool isSubscribed, string application) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + var user = dbContext.Users + .AsNoTracking() + .Where(r => r.UserId == userId) + .Where(r => r.TenantId == tenantId) + .Where(r => r.Application == application) + .Where(r => r.FirebaseDeviceToken == fbDeviceToken) + .FirstOrDefault(); + + + if (user == null) + { + var newUser = new FireBaseUser + { + UserId = userId, + TenantId = tenantId, + FirebaseDeviceToken = fbDeviceToken, + IsSubscribed = isSubscribed, + Application = application + }; + dbContext.Add(newUser); + dbContext.SaveChanges(); + + return newUser; + } + + return user; + } + + public List GetUserDeviceTokens(Guid userId, int tenantId, string application) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + return dbContext.Users + .AsNoTracking() + .Where(r => r.UserId == userId) + .Where(r => r.TenantId == tenantId) + .Where(r => r.Application == application) + .ToList(); + } + + public FireBaseUser UpdateUser(Guid userId, int tenantId, string fbDeviceToken, bool isSubscribed, string application) + { + using var dbContext = _dbContextFactory.CreateDbContext(); + var user = new FireBaseUser + { + UserId = userId, + TenantId = tenantId, + FirebaseDeviceToken = fbDeviceToken, + IsSubscribed = isSubscribed, + Application = application + }; + + dbContext.Update(user); + dbContext.SaveChanges(); + + return user; + } + +} diff --git a/common/services/ASC.Webhooks.Service/Services/BuildQueueService.cs b/common/ASC.Core.Common/Notify/Push/Dao/NotifyData.cs similarity index 64% rename from common/services/ASC.Webhooks.Service/Services/BuildQueueService.cs rename to common/ASC.Core.Common/Notify/Push/Dao/NotifyData.cs index 69d3065767..8f6c1f64cd 100644 --- a/common/services/ASC.Webhooks.Service/Services/BuildQueueService.cs +++ b/common/ASC.Core.Common/Notify/Push/Dao/NotifyData.cs @@ -24,33 +24,23 @@ // 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.Service.Services; +namespace ASC.Core.Common.Notify.Push.Dao; -[Singletone] -public class BuildQueueService : BackgroundService +[Serializable] +public class NotifyData { - internal readonly ConcurrentQueue Queue; - private readonly ICacheNotify _webhookNotify; + [JsonProperty("portal")] + public string Portal { get; set; } - public BuildQueueService(ICacheNotify webhookNotify) - { - _webhookNotify = webhookNotify; - Queue = new ConcurrentQueue(); - } - public void BuildWebhooksQueue(WebhookRequest request) - { - Queue.Enqueue(request); - } + [JsonProperty("email")] + public string Email { get; set; } - protected override Task ExecuteAsync(CancellationToken stoppingToken) - { - _webhookNotify.Subscribe(BuildWebhooksQueue, CacheNotifyAction.Update); + [JsonProperty("file")] + public NotifyFileData File { get; set; } - stoppingToken.Register(() => - { - _webhookNotify.Unsubscribe(CacheNotifyAction.Update); - }); + [JsonProperty("folder")] + public NotifyFolderData Folder { get; set; } - return Task.CompletedTask; - } -} \ No newline at end of file + [JsonProperty("originalUrl")] + public string OriginalUrl { get; set; } +} diff --git a/products/ASC.Files/Core/Services/OFormService/OFormService.cs b/common/ASC.Core.Common/Notify/Push/Dao/NotifyFileData.cs similarity index 64% rename from products/ASC.Files/Core/Services/OFormService/OFormService.cs rename to common/ASC.Core.Common/Notify/Push/Dao/NotifyFileData.cs index 22fb40596d..85b3237e66 100644 --- a/products/ASC.Files/Core/Services/OFormService/OFormService.cs +++ b/common/ASC.Core.Common/Notify/Push/Dao/NotifyFileData.cs @@ -1,49 +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.Files.Core.Services.OFormService; - -[Singletone] -public sealed class OFormService : BackgroundService -{ - private readonly TimeSpan _formPeriod; - private readonly OFormRequestManager _oFormRequestManager; - - public OFormService(OFormRequestManager oFormRequestManager, ConfigurationExtension configurationExtension) - { - _oFormRequestManager = oFormRequestManager; - _formPeriod = TimeSpan.FromSeconds(configurationExtension.GetSetting("files:oform").Period); - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - await _oFormRequestManager.Init(stoppingToken); - await Task.Delay(_formPeriod, stoppingToken); - } - } -} +// (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.Core.Common.Notify.Push.Dao; + +[Serializable] +public class NotifyFileData +{ + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("title")] + public string Title { get; set; } + [JsonProperty("extension")] + public string Extension { get; set; } + +} diff --git a/common/ASC.Core.Common/Notify/Push/Dao/NotifyFolderData.cs b/common/ASC.Core.Common/Notify/Push/Dao/NotifyFolderData.cs new file mode 100644 index 0000000000..f15bf22b9c --- /dev/null +++ b/common/ASC.Core.Common/Notify/Push/Dao/NotifyFolderData.cs @@ -0,0 +1,37 @@ +// (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.Core.Common.Notify.Push.Dao; + +public class NotifyFolderData +{ + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("parentId")] + public string ParentId { get; set; } + [JsonProperty("rootFolderType")] + public int RootFolderType { get; set; } +} diff --git a/common/ASC.Core.Common/Notify/Push/FirebaseHelper.cs b/common/ASC.Core.Common/Notify/Push/FirebaseHelper.cs new file mode 100644 index 0000000000..cb6d6bae44 --- /dev/null +++ b/common/ASC.Core.Common/Notify/Push/FirebaseHelper.cs @@ -0,0 +1,127 @@ +// (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.Core.Common.Notify.Push; + +[Scope] +public class FirebaseHelper +{ + protected readonly UserManager _userManager; + private readonly AuthContext _authContext; + private readonly TenantManager _tenantManager; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + private readonly FirebaseDao _firebaseDao; + + public FirebaseHelper( + AuthContext authContext, + UserManager userManager, + TenantManager tenantManager, + IConfiguration configuration, + ILogger logger, + FirebaseDao firebaseDao) + { + _authContext = authContext; + _userManager = userManager; + _tenantManager = tenantManager; + _configuration = configuration; + _logger = logger; + _firebaseDao = firebaseDao; + } + + public void SendMessage(NotifyMessage msg) + { + var defaultInstance = FirebaseApp.DefaultInstance; + if (defaultInstance == null) + { + try + { + var credentials = JsonConvert.SerializeObject(new FirebaseApiKey(_configuration)).Replace("\\\\", "\\"); + FirebaseApp.Create(new AppOptions() + { + Credential = GoogleCredential.FromJson(credentials) + }); + } + catch (Exception e) + { + _logger.ErrorUnexpected(e); + } + } + + _tenantManager.SetCurrentTenant(msg.TenantId); + + var user = _userManager.GetUserByUserName(msg.Reciever); + + Guid productID; + + if (!Guid.TryParse(msg.ProductID, out productID)) + { + return; + } + + var fireBaseUser = new List(); + + if (productID == new Guid("{E67BE73D-F9AE-4ce1-8FEC-1880CB518CB4}")) //documents product + { + fireBaseUser = _firebaseDao.GetUserDeviceTokens(user.Id, msg.TenantId, PushConstants.PushDocAppName); + } + + foreach (var fb in fireBaseUser) + { + if (fb.IsSubscribed.HasValue && fb.IsSubscribed.Value == true) + { + var m = new FirebaseAdminMessaging.Message() + { + Data = new Dictionary{ + { "data", msg.Data } + }, + Token = fb.FirebaseDeviceToken, + Notification = new FirebaseAdminMessaging.Notification() + { + Body = msg.Content + } + }; + FirebaseAdminMessaging.FirebaseMessaging.DefaultInstance.SendAsync(m); + } + } + } + + public FireBaseUser RegisterUserDevice(string fbDeviceToken, bool isSubscribed, string application) + { + var userId = _authContext.CurrentAccount.ID; + var tenantId = _tenantManager.GetCurrentTenant().Id; + + return _firebaseDao.RegisterUserDevice(userId, tenantId, fbDeviceToken, isSubscribed, application); + } + + public FireBaseUser UpdateUser(string fbDeviceToken, bool isSubscribed, string application) + { + var userId = _authContext.CurrentAccount.ID; + var tenantId = _tenantManager.GetCurrentTenant().Id; + + return _firebaseDao.UpdateUser(userId, tenantId, fbDeviceToken, isSubscribed, application); + } +} diff --git a/common/ASC.Core.Common/Notify/Push/PushConstants.cs b/common/ASC.Core.Common/Notify/Push/PushConstants.cs index 97e5443a4f..09868069d1 100644 --- a/common/ASC.Core.Common/Notify/Push/PushConstants.cs +++ b/common/ASC.Core.Common/Notify/Push/PushConstants.cs @@ -32,4 +32,6 @@ public static class PushConstants public const string PushParentItemTagName = "PushParentItem"; public const string PushModuleTagName = "PushModule"; public const string PushActionTagName = "PushAction"; + + public const string PushDocAppName = "doc"; } diff --git a/common/ASC.Core.Common/Notify/PushSenderSink.cs b/common/ASC.Core.Common/Notify/PushSenderSink.cs index a148958343..43e2228b48 100644 --- a/common/ASC.Core.Common/Notify/PushSenderSink.cs +++ b/common/ASC.Core.Common/Notify/PushSenderSink.cs @@ -24,19 +24,22 @@ // 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.Core.Common.Notify.Push.Dao; + using Constants = ASC.Core.Configuration.Constants; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace ASC.Core.Common.Notify; class PushSenderSink : Sink { - private readonly ILogger _logger; - private bool _configured = true; + private static readonly string _senderName = Constants.NotifyPushSenderSysName; + private readonly INotifySender _sender; - public PushSenderSink(IServiceProvider serviceProvider, ILogger logger) + public PushSenderSink(INotifySender sender, IServiceProvider serviceProvider) { + _sender = sender ?? throw new ArgumentNullException(nameof(sender)); _serviceProvider = serviceProvider; - _logger = logger; } private readonly IServiceProvider _serviceProvider; @@ -45,42 +48,21 @@ class PushSenderSink : Sink { try { + + var result = SendResult.OK; using var scope = _serviceProvider.CreateScope(); - var tenantManager = scope.ServiceProvider.GetService(); - var notification = new PushNotification + var m = scope.ServiceProvider.GetRequiredService().CreateNotifyMessage(message, _senderName); + if (string.IsNullOrEmpty(m.Reciever)) { - Module = GetTagValue(message, PushConstants.PushModuleTagName), - Action = GetTagValue(message, PushConstants.PushActionTagName), - Item = GetTagValue(message, PushConstants.PushItemTagName), - ParentItem = GetTagValue(message, PushConstants.PushParentItemTagName), - Message = message.Body, - ShortMessage = message.Subject - }; - - if (_configured) - { - try - { - using var pushClient = new PushServiceClient(); - pushClient.EnqueueNotification( - tenantManager.GetCurrentTenant().Id, - message.Recipient.ID, - notification, - new List()); - } - catch (InvalidOperationException) - { - _configured = false; - _logger.DebugPushSender(); - } + result = SendResult.IncorrectRecipient; } else { - _logger.DebugPushSender(); + _sender.Send(m); } - return new SendResponse(message, Constants.NotifyPushSenderSysName, SendResult.OK); + return new SendResponse(message, Constants.NotifyPushSenderSysName, result); } catch (Exception error) { @@ -95,3 +77,95 @@ class PushSenderSink : Sink return tag != null ? (T)tag.Value : default; } } +public class LowerCaseNamingPolicy : JsonNamingPolicy +{ + public override string ConvertName(string name) => + name.ToLower(); +} +[Scope] +public class PushSenderSinkMessageCreator : SinkMessageCreator +{ + private readonly UserManager _userManager; + private readonly TenantManager _tenantManager; + + public PushSenderSinkMessageCreator(UserManager userManager, TenantManager tenantManager) + { + _tenantManager = tenantManager; + _userManager = userManager; + } + + public override NotifyMessage CreateNotifyMessage(INoticeMessage message, string senderName) + { + var tenant = _tenantManager.GetCurrentTenant(false); + if (tenant == null) + { + _tenantManager.SetCurrentTenant(Tenant.DefaultTenant); + tenant = _tenantManager.GetCurrentTenant(false); + } + + var user = _userManager.GetUsers(new Guid(message.Recipient.ID)); + var username = user.UserName; + + var fromTag = message.Arguments.FirstOrDefault(x => x.Tag.Equals("MessageFrom")); + var productID = message.Arguments.FirstOrDefault(x => x.Tag.Equals("__ProductID")); + var originalUrl = message.Arguments.FirstOrDefault(x => x.Tag.Equals("DocumentURL")); + + var folderId = message.Arguments.FirstOrDefault(x => x.Tag.Equals("FolderID")); + var rootFolderId = message.Arguments.FirstOrDefault(x => x.Tag.Equals("FolderParentId")); + var rootFolderType = message.Arguments.FirstOrDefault(x => x.Tag.Equals("FolderRootFolderType")); + + + var notifyData = new NotifyData() + { + Email = user.Email, + Portal = _tenantManager.GetCurrentTenant().TrustedDomains.FirstOrDefault(), + OriginalUrl = originalUrl != null && originalUrl.Value != null ? originalUrl.Value.ToString() : "", + Folder = new NotifyFolderData + { + Id = folderId != null && folderId.Value != null ? folderId.Value.ToString() : "", + ParentId = rootFolderId != null && rootFolderId.Value != null ? rootFolderId.Value.ToString() : "", + RootFolderType = rootFolderType != null && rootFolderType.Value != null ? (int)rootFolderType.Value : 0 + }, + }; + + var msg = (NoticeMessage)message; + + if (msg.ObjectID.StartsWith("file_")) + { + var documentTitle = message.Arguments.FirstOrDefault(x => x.Tag.Equals("DocumentTitle")); + var documentExtension = message.Arguments.FirstOrDefault(x => x.Tag.Equals("DocumentExtension")); + + notifyData.File = new NotifyFileData() + { + Id = msg.ObjectID.Substring(5), + Title = documentTitle != null && documentTitle.Value != null ? documentTitle.Value.ToString() : "", + Extension = documentExtension != null && documentExtension.Value != null ? documentExtension.Value.ToString() : "" + + }; + } + + var serializeOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = new LowerCaseNamingPolicy(), + WriteIndented = true + }; + + var jsonNotifyData = JsonSerializer.Serialize(notifyData, serializeOptions); + + var m = new NotifyMessage + { + TenantId = tenant.Id, + Reciever = username, + Subject = fromTag != null && fromTag.Value != null ? fromTag.Value.ToString() : message.Subject, + ContentType = message.ContentType, + Content = message.Body, + Sender = Constants.NotifyPushSenderSysName, + CreationDate = DateTime.UtcNow, + ProductID = fromTag != null && fromTag.Value != null ? productID.Value.ToString() : null, + Data = jsonNotifyData + }; + + + return m; + } +} diff --git a/common/ASC.Core.Common/Notify/Senders/PushSender.cs b/common/ASC.Core.Common/Notify/Senders/PushSender.cs new file mode 100644 index 0000000000..427abc673a --- /dev/null +++ b/common/ASC.Core.Common/Notify/Senders/PushSender.cs @@ -0,0 +1,71 @@ +// (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.Core.Notify.Senders; + +[Singletone(Additional = typeof(FirebaseSenderExtension))] +public class PushSender : INotifySender +{ + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + + public PushSender(ILoggerProvider options, IServiceProvider serviceProvider) + { + _logger = options.CreateLogger("ASC.Notify"); + _serviceProvider = serviceProvider; + } + + + public void Init(IDictionary properties) { } + + public NoticeSendResult Send(NotifyMessage m) + { + if (!string.IsNullOrEmpty(m.Content)) + { + m.Content = m.Content.Replace("\r\n", "\n").Trim('\n', '\r', ' '); + m.Content = Regex.Replace(m.Content, "\n{3,}", "\n\n"); + } + try + { + using var scope = _serviceProvider.CreateScope(); + var FirebaseHelper = scope.ServiceProvider.GetService(); + FirebaseHelper.SendMessage(m); + } + catch (Exception e) + { + _logger.ErrorUnexpected(e); + } + + return NoticeSendResult.OK; + } +} +public static class FirebaseSenderExtension +{ + public static void Register(DIHelper services) + { + services.TryAdd(); + } +} diff --git a/common/ASC.Webhooks.Core/DbWorker.cs b/common/ASC.Webhooks.Core/DbWorker.cs index 1b73b7282c..3704f51bfe 100644 --- a/common/ASC.Webhooks.Core/DbWorker.cs +++ b/common/ASC.Webhooks.Core/DbWorker.cs @@ -30,117 +30,186 @@ namespace ASC.Webhooks.Core; public class DbWorker { private readonly IDbContextFactory _dbContextFactory; - private readonly TenantManager _tenantManager; - public DbWorker(IDbContextFactory dbContextFactory, TenantManager tenantManager) + private readonly TenantManager _tenantManager; + private readonly AuthContext _authContext; + + private int Tenant + { + get + { + return _tenantManager.GetCurrentTenant().Id; + } + } + + public DbWorker(IDbContextFactory dbContextFactory, TenantManager tenantManager, AuthContext authContext) { _dbContextFactory = dbContextFactory; - _tenantManager = tenantManager; - } - public void AddWebhookConfig(WebhooksConfig webhooksConfig) - { - webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id; - - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + _tenantManager = tenantManager; + _authContext = authContext; + } - var addObj = webhooksDbContext.WebhooksConfigs.Where(it => - it.SecretKey == webhooksConfig.SecretKey && - it.TenantId == webhooksConfig.TenantId && - it.Uri == webhooksConfig.Uri).FirstOrDefault(); - - if (addObj != null) - { - return; - } - - webhooksDbContext.WebhooksConfigs.Add(webhooksConfig); - webhooksDbContext.SaveChanges(); - } - - public int ConfigsNumber() + public async Task AddWebhookConfig(string name, string uri, string secretKey) { using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - return webhooksDbContext.WebhooksConfigs.Count(); + + var toAdd = new WebhooksConfig { TenantId = Tenant, Uri = uri, SecretKey = secretKey, Name = name }; + toAdd = await webhooksDbContext.AddOrUpdateAsync(r => r.WebhooksConfigs, toAdd); + await webhooksDbContext.SaveChangesAsync(); + + return toAdd; } - public List GetTenantWebhooks() + public async IAsyncEnumerable GetTenantWebhooks() + { + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + var q = webhooksDbContext.WebhooksConfigs + .AsNoTracking() + .Where(it => it.TenantId == Tenant) + .AsAsyncEnumerable(); + + await foreach (var webhook in q) + { + yield return webhook; + } + } + + public IAsyncEnumerable GetWebhookConfigs() + { + var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + return webhooksDbContext.WebhooksConfigs + .Where(t => t.TenantId == Tenant) + .AsAsyncEnumerable(); + } + + public async Task UpdateWebhookConfig(int id, string name, string uri, string key, bool? enabled) + { + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + var updateObj = await webhooksDbContext.WebhooksConfigs + .Where(it => it.TenantId == Tenant && it.Id == id) + .FirstOrDefaultAsync(); + + if (updateObj != null) + { + if (!string.IsNullOrEmpty(uri)) + { + updateObj.Uri = uri; + } + + if (!string.IsNullOrEmpty(name)) + { + updateObj.Name = name; + } + + if (!string.IsNullOrEmpty(key)) + { + updateObj.SecretKey = key; + } + + if (enabled.HasValue) + { + updateObj.Enabled = enabled.Value; + } + + webhooksDbContext.WebhooksConfigs.Update(updateObj); + await webhooksDbContext.SaveChangesAsync(); + } + + return updateObj; + } + + public async Task RemoveWebhookConfig(int id) { var tenant = _tenantManager.GetCurrentTenant().Id; - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - return webhooksDbContext.WebhooksLogs.Where(it => it.TenantId == tenant) - .Select(t => new WebhooksLog - { - Uid = t.Uid, - CreationTime = t.CreationTime, - RequestPayload = t.RequestPayload, - RequestHeaders = t.RequestHeaders, - ResponsePayload = t.ResponsePayload, - ResponseHeaders = t.ResponseHeaders, - Status = t.Status - }).ToList(); - } - - public List GetWebhookConfigs(int tenant) - { - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - return webhooksDbContext.WebhooksConfigs.Where(t => t.TenantId == tenant).ToList(); - } - - public WebhookEntry ReadFromJournal(int id) - { - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - return webhooksDbContext.WebhooksLogs - .Where(it => it.Id == id) - .Join(webhooksDbContext.WebhooksConfigs, t => t.ConfigId, t => t.ConfigId, (payload, config) => new { payload, config }) - .Select(t => new WebhookEntry { Id = t.payload.Id, Payload = t.payload.RequestPayload, SecretKey = t.config.SecretKey, Uri = t.config.Uri }) - .OrderBy(t => t.Id).FirstOrDefault(); - } - - public void RemoveWebhookConfig(WebhooksConfig webhooksConfig) - { - webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id; + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - var removeObj = webhooksDbContext.WebhooksConfigs.Where(it => - it.SecretKey == webhooksConfig.SecretKey && - it.TenantId == webhooksConfig.TenantId && - it.Uri == webhooksConfig.Uri).FirstOrDefault(); + var removeObj = await webhooksDbContext.WebhooksConfigs + .Where(it => it.TenantId == tenant && it.Id == id) + .FirstOrDefaultAsync(); webhooksDbContext.WebhooksConfigs.Remove(removeObj); - webhooksDbContext.SaveChanges(); - } - - public void UpdateWebhookConfig(WebhooksConfig webhooksConfig) - { - webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id; + await webhooksDbContext.SaveChangesAsync(); - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - var updateObj = webhooksDbContext.WebhooksConfigs.Where(it => - it.SecretKey == webhooksConfig.SecretKey && - it.TenantId == webhooksConfig.TenantId && - it.Uri == webhooksConfig.Uri).FirstOrDefault(); - - webhooksDbContext.WebhooksConfigs.Update(updateObj); - webhooksDbContext.SaveChanges(); - } - - public void UpdateWebhookJournal(int id, ProcessStatus status, string responsePayload, string responseHeaders, string requestHeaders) + return removeObj; + } + + public IAsyncEnumerable ReadJournal(int startIndex, int limit, DateTime? delivery, string hookname, string route) { - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - var webhook = webhooksDbContext.WebhooksLogs.Where(t => t.Id == id).FirstOrDefault(); + var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + var q = webhooksDbContext.WebhooksLogs + .AsNoTracking() + .Where(r => r.TenantId == Tenant); + + if (delivery.HasValue) + { + var date = delivery.Value; + q = q.Where(r => r.Delivery == date); + } + + if (!string.IsNullOrEmpty(hookname)) + { + q = q.Where(r => r.Config.Name == hookname); + } + + if (!string.IsNullOrEmpty(route)) + { + q = q.Where(r => r.Route == route); + } + + if (startIndex != 0) + { + q = q.Skip(startIndex); + } + + if (limit != 0) + { + q = q.Take(limit); + } + + return q.OrderByDescending(t => t.Id).AsAsyncEnumerable(); + } + + public async Task ReadJournal(int id) + { + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + return await webhooksDbContext.WebhooksLogs + .AsNoTracking() + .Where(it => it.Id == id) + .FirstOrDefaultAsync(); + } + + public async Task WriteToJournal(WebhooksLog webhook) + { + webhook.TenantId = _tenantManager.GetCurrentTenant().Id; + webhook.Uid = _authContext.CurrentAccount.ID; + + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + var entity = await webhooksDbContext.WebhooksLogs.AddAsync(webhook); + await webhooksDbContext.SaveChangesAsync(); + + return entity.Entity; + } + + public async Task UpdateWebhookJournal(int id, int status, DateTime delivery, string requestHeaders, string responsePayload, string responseHeaders) + { + using var webhooksDbContext = _dbContextFactory.CreateDbContext(); + + var webhook = await webhooksDbContext.WebhooksLogs.Where(t => t.Id == id).FirstOrDefaultAsync(); webhook.Status = status; + webhook.RequestHeaders = requestHeaders; webhook.ResponsePayload = responsePayload; - webhook.ResponseHeaders = responseHeaders; - webhook.RequestHeaders = requestHeaders; - webhooksDbContext.WebhooksLogs.Update(webhook); - webhooksDbContext.SaveChanges(); - } + webhook.ResponseHeaders = responseHeaders; + webhook.Delivery = delivery; - public int WriteToJournal(WebhooksLog webhook) - { - using var webhooksDbContext = _dbContextFactory.CreateDbContext(); - var entity = webhooksDbContext.WebhooksLogs.Add(webhook); - webhooksDbContext.SaveChanges(); - return entity.Entity.Id; + webhooksDbContext.WebhooksLogs.Update(webhook); + await webhooksDbContext.SaveChangesAsync(); + + return webhook; } } \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/EF/Context/WebhooksDbContext.cs b/common/ASC.Webhooks.Core/EF/Context/WebhooksDbContext.cs index cb436c8ad9..661ece604b 100644 --- a/common/ASC.Webhooks.Core/EF/Context/WebhooksDbContext.cs +++ b/common/ASC.Webhooks.Core/EF/Context/WebhooksDbContext.cs @@ -28,8 +28,8 @@ namespace ASC.Webhooks.Core.EF.Context; public class WebhooksDbContext : DbContext { - public virtual DbSet WebhooksConfigs { get; set; } - public virtual DbSet WebhooksLogs { get; set; } + public DbSet WebhooksConfigs { get; set; } + public DbSet WebhooksLogs { get; set; } public WebhooksDbContext(DbContextOptions options) : base(options) { } diff --git a/common/ASC.Webhooks.Core/EF/Model/WebhooksConfig.cs b/common/ASC.Webhooks.Core/EF/Model/WebhooksConfig.cs index b5832d00ed..2232cb3bf9 100644 --- a/common/ASC.Webhooks.Core/EF/Model/WebhooksConfig.cs +++ b/common/ASC.Webhooks.Core/EF/Model/WebhooksConfig.cs @@ -25,12 +25,20 @@ // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode namespace ASC.Webhooks.Core.EF.Model; -public partial class WebhooksConfig + +public class WebhooksConfig : BaseEntity { - public int ConfigId { get; set; } + public int Id { get; set; } + public string Name { get; set; } public string SecretKey { get; set; } public int TenantId { get; set; } - public string Uri { get; set; } + public string Uri { get; set; } + public bool Enabled { get; set; } + + public override object[] GetKeys() + { + return new object[] { Id }; + } } public static class WebhooksConfigExtension @@ -46,15 +54,18 @@ public static class WebhooksConfigExtension { modelBuilder.Entity(entity => { - entity.HasKey(e => new { e.ConfigId }) - .HasName("PRIMARY"); + entity.HasKey(e => new { e.Id }) + .HasName("PRIMARY"); + + entity.HasIndex(e => e.TenantId) + .HasDatabaseName("tenant_id"); entity.ToTable("webhooks_config") .HasCharSet("utf8"); - entity.Property(e => e.ConfigId) + entity.Property(e => e.Id) .HasColumnType("int") - .HasColumnName("config_id"); + .HasColumnName("id"); entity.Property(e => e.TenantId) .HasColumnName("tenant_id") @@ -68,7 +79,17 @@ public static class WebhooksConfigExtension entity.Property(e => e.SecretKey) .HasMaxLength(50) .HasColumnName("secret_key") - .HasDefaultValueSql("''"); + .HasDefaultValueSql("''"); + + entity.Property(e => e.Name) + .HasMaxLength(50) + .HasColumnName("name") + .IsRequired(); + + entity.Property(e => e.Enabled) + .HasColumnName("enabled") + .HasDefaultValueSql("'1'") + .HasColumnType("tinyint(1)"); }); } @@ -76,14 +97,17 @@ public static class WebhooksConfigExtension { modelBuilder.Entity(entity => { - entity.HasKey(e => new { e.ConfigId }) + entity.HasKey(e => new { e.Id }) .HasName("PRIMARY"); - entity.ToTable("webhooks_config"); + entity.ToTable("webhooks_config"); + + entity.HasIndex(e => e.TenantId) + .HasDatabaseName("tenant_id"); - entity.Property(e => e.ConfigId) + entity.Property(e => e.Id) .HasColumnType("int") - .HasColumnName("config_id"); + .HasColumnName("id"); entity.Property(e => e.TenantId) .HasColumnName("tenant_id") @@ -97,7 +121,16 @@ public static class WebhooksConfigExtension entity.Property(e => e.SecretKey) .HasMaxLength(50) .HasColumnName("secret_key") - .HasDefaultValueSql("''"); + .HasDefaultValueSql("''"); + + entity.Property(e => e.Name) + .HasMaxLength(50) + .HasColumnName("name") + .IsRequired(); + + entity.Property(e => e.Enabled) + .HasColumnName("enabled") + .HasDefaultValueSql("true"); }); } } diff --git a/common/ASC.Webhooks.Core/EF/Model/WebhooksLog.cs b/common/ASC.Webhooks.Core/EF/Model/WebhooksLog.cs index 8766f3b17e..3daa62b4c1 100644 --- a/common/ASC.Webhooks.Core/EF/Model/WebhooksLog.cs +++ b/common/ASC.Webhooks.Core/EF/Model/WebhooksLog.cs @@ -26,25 +26,31 @@ namespace ASC.Webhooks.Core.EF.Model; -public partial class WebhooksLog +public class WebhooksLog { public int ConfigId { get; set; } public DateTime CreationTime { get; set; } - public string Event { get; set; } - public int Id { get; set; } + public int Id { get; set; } + public string Method { get; set; } + public string Route { get; set; } public string RequestHeaders { get; set; } public string RequestPayload { get; set; } public string ResponseHeaders { get; set; } public string ResponsePayload { get; set; } - public ProcessStatus Status { get; set; } + public int Status { get; set; } public int TenantId { get; set; } - public string Uid { get; set; } + public Guid Uid { get; set; } + public DateTime? Delivery { get; set; } + + public WebhooksConfig Config { get; set; } } public static class WebhooksPayloadExtension { public static ModelBuilderWrapper AddWebhooksLog(this ModelBuilderWrapper modelBuilder) - { + { + modelBuilder.Entity().Navigation(e => e.Config).AutoInclude(); + modelBuilder .Add(MySqlAddWebhooksLog, Provider.MySql) .Add(PgSqlAddWebhooksLog, Provider.PostgreSql); @@ -60,7 +66,10 @@ public static class WebhooksPayloadExtension .HasName("PRIMARY"); entity.ToTable("webhooks_logs") - .HasCharSet("utf8"); + .HasCharSet("utf8"); + + entity.HasIndex(e => e.TenantId) + .HasDatabaseName("tenant_id"); entity.Property(e => e.Id) .HasColumnType("int") @@ -72,9 +81,10 @@ public static class WebhooksPayloadExtension .HasColumnName("config_id"); entity.Property(e => e.Uid) - .HasColumnType("varchar") - .HasColumnName("uid") - .HasMaxLength(50); + .HasColumnName("uid") + .HasColumnType("varchar(36)") + .HasCharSet("utf8") + .UseCollation("utf8_general_ci"); entity.Property(e => e.TenantId) .HasColumnName("tenant_id") @@ -83,7 +93,9 @@ public static class WebhooksPayloadExtension entity.Property(e => e.RequestPayload) .IsRequired() .HasColumnName("request_payload") - .HasColumnType("json"); + .HasColumnType("text") + .HasCharSet("utf8") + .UseCollation("utf8_general_ci"); entity.Property(e => e.RequestHeaders) .HasColumnName("request_headers") @@ -91,25 +103,35 @@ public static class WebhooksPayloadExtension entity.Property(e => e.ResponsePayload) .HasColumnName("response_payload") - .HasColumnType("json"); + .HasColumnType("text") + .HasCharSet("utf8") + .UseCollation("utf8_general_ci"); entity.Property(e => e.ResponseHeaders) .HasColumnName("response_headers") .HasColumnType("json"); - entity.Property(e => e.Event) + entity.Property(e => e.Method) .HasColumnType("varchar") - .HasColumnName("event") + .HasColumnName("method") + .HasMaxLength(100); + + entity.Property(e => e.Route) + .HasColumnType("varchar") + .HasColumnName("route") .HasMaxLength(100); entity.Property(e => e.CreationTime) .HasColumnType("datetime") .HasColumnName("creation_time"); + entity.Property(e => e.Delivery) + .HasColumnType("datetime") + .HasColumnName("delivery"); + entity.Property(e => e.Status) - .HasColumnType("varchar") - .HasColumnName("status") - .HasMaxLength(50); + .HasColumnType("int") + .HasColumnName("status"); }); } @@ -120,7 +142,10 @@ public static class WebhooksPayloadExtension entity.HasKey(e => new { e.Id }) .HasName("PRIMARY"); - entity.ToTable("webhooks_logs"); + entity.ToTable("webhooks_logs"); + + entity.HasIndex(e => e.TenantId) + .HasDatabaseName("tenant_id"); entity.Property(e => e.Id) .HasColumnType("int") @@ -142,34 +167,40 @@ public static class WebhooksPayloadExtension entity.Property(e => e.RequestPayload) .IsRequired() - .HasColumnName("request_payload") - .HasColumnType("json"); + .HasColumnName("request_payload"); entity.Property(e => e.RequestHeaders) .HasColumnName("request_headers") .HasColumnType("json"); entity.Property(e => e.ResponsePayload) - .HasColumnName("response_payload") - .HasColumnType("json"); + .HasColumnName("response_payload"); entity.Property(e => e.ResponseHeaders) .HasColumnName("response_headers") .HasColumnType("json"); - entity.Property(e => e.Event) + entity.Property(e => e.Method) .HasColumnType("varchar") - .HasColumnName("event") + .HasColumnName("method") + .HasMaxLength(100); + + entity.Property(e => e.Route) + .HasColumnType("varchar") + .HasColumnName("route") .HasMaxLength(100); entity.Property(e => e.CreationTime) .HasColumnType("datetime") - .HasColumnName("creation_time"); + .HasColumnName("creation_time"); + + entity.Property(e => e.Delivery) + .HasColumnType("datetime") + .HasColumnName("delivery"); entity.Property(e => e.Status) - .HasColumnType("varchar") - .HasColumnName("status") - .HasMaxLength(50); + .HasColumnType("int") + .HasColumnName("status"); }); } } \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/IWebhookPublisher.cs b/common/ASC.Webhooks.Core/IWebhookPublisher.cs index ec9c529c26..be00d4bd37 100644 --- a/common/ASC.Webhooks.Core/IWebhookPublisher.cs +++ b/common/ASC.Webhooks.Core/IWebhookPublisher.cs @@ -29,5 +29,6 @@ namespace ASC.Webhooks.Core; [Scope] public interface IWebhookPublisher { - public void Publish(string eventName, string requestPayload); + public Task PublishAsync(string method, string route, string requestPayload); + public Task PublishAsync(string method, string route, string requestPayload, int configId); } \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/WebhookPublisher.cs b/common/ASC.Webhooks.Core/WebhookPublisher.cs index 42edf23d69..f1d459b825 100644 --- a/common/ASC.Webhooks.Core/WebhookPublisher.cs +++ b/common/ASC.Webhooks.Core/WebhookPublisher.cs @@ -30,51 +30,56 @@ namespace ASC.Webhooks.Core; public class WebhookPublisher : IWebhookPublisher { private readonly DbWorker _dbWorker; - private readonly TenantManager _tenantManager; private readonly ICacheNotify _webhookNotify; - + public WebhookPublisher( DbWorker dbWorker, - TenantManager tenantManager, ICacheNotify webhookNotify) { _dbWorker = dbWorker; - _tenantManager = tenantManager; _webhookNotify = webhookNotify; } - public void Publish(string eventName, string requestPayload) + public async Task PublishAsync(string method, string route, string requestPayload) { - var tenantId = _tenantManager.GetCurrentTenant().Id; - var webhookConfigs = _dbWorker.GetWebhookConfigs(tenantId); - - foreach (var config in webhookConfigs) + if (string.IsNullOrEmpty(requestPayload)) { - var webhooksLog = new WebhooksLog - { - Uid = Guid.NewGuid().ToString(), - TenantId = tenantId, - Event = eventName, - CreationTime = DateTime.UtcNow, - RequestPayload = requestPayload, - Status = ProcessStatus.InProcess, - ConfigId = config.ConfigId - }; - var DbId = _dbWorker.WriteToJournal(webhooksLog); + return; + } + + var webhookConfigs = _dbWorker.GetWebhookConfigs(); - var request = new WebhookRequest() - { - Id = DbId - }; - - _webhookNotify.Publish(request, CacheNotifyAction.Update); + await foreach (var config in webhookConfigs.Where(r => r.Enabled)) + { + _ = await PublishAsync(method, route, requestPayload, config.Id); } } -} -public enum ProcessStatus -{ - InProcess, - Success, - Failed + public async Task PublishAsync(string method, string route, string requestPayload, int configId) + { + if (string.IsNullOrEmpty(requestPayload)) + { + return null; + } + + var webhooksLog = new WebhooksLog + { + Method = method, + Route = route, + CreationTime = DateTime.UtcNow, + RequestPayload = requestPayload, + ConfigId = configId + }; + + var webhook = await _dbWorker.WriteToJournal(webhooksLog); + + var request = new WebhookRequest + { + Id = webhook.Id + }; + + _webhookNotify.Publish(request, CacheNotifyAction.Update); + + return webhook; + } } diff --git a/common/Tools/ASC.Migration.Creator/Program.cs b/common/Tools/ASC.Migration.Creator/Program.cs index cd1c5a4415..ec66e57bec 100644 --- a/common/Tools/ASC.Migration.Creator/Program.cs +++ b/common/Tools/ASC.Migration.Creator/Program.cs @@ -58,6 +58,7 @@ builder.WebHost.ConfigureServices((hostContext, services) => services.AddBaseDbContext(); services.AddBaseDbContext(); services.AddBaseDbContext(); + services.AddBaseDbContext(); }); var app = builder.Build(); diff --git a/common/Tools/ASC.Migration.Runner/Program.cs b/common/Tools/ASC.Migration.Runner/Program.cs index a31f647aa0..debe36b179 100644 --- a/common/Tools/ASC.Migration.Runner/Program.cs +++ b/common/Tools/ASC.Migration.Runner/Program.cs @@ -45,6 +45,7 @@ builder.WebHost.ConfigureServices((hostContext, services) => services.AddBaseDbContext(); services.AddBaseDbContext(); services.AddBaseDbContext(); + services.AddBaseDbContext(); services.AddBaseDbContext(); services.AddBaseDbContext(); services.AddBaseDbContext(); diff --git a/common/services/ASC.Webhooks.Service/ASC.Webhooks.Service.csproj b/common/services/ASC.Webhooks.Service/ASC.Webhooks.Service.csproj index 0b8cffcb95..0c58b87659 100644 --- a/common/services/ASC.Webhooks.Service/ASC.Webhooks.Service.csproj +++ b/common/services/ASC.Webhooks.Service/ASC.Webhooks.Service.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/common/services/ASC.Webhooks.Service/GlobalUsings.cs b/common/services/ASC.Webhooks.Service/GlobalUsings.cs index 41aae62e4c..33df612814 100644 --- a/common/services/ASC.Webhooks.Service/GlobalUsings.cs +++ b/common/services/ASC.Webhooks.Service/GlobalUsings.cs @@ -37,9 +37,13 @@ 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 Microsoft.Extensions.Logging; + +global using Polly; +global using Polly.Extensions.Http; \ No newline at end of file diff --git a/common/services/ASC.Webhooks.Service/Program.cs b/common/services/ASC.Webhooks.Service/Program.cs index 1171ef1184..b264c9e053 100644 --- a/common/services/ASC.Webhooks.Service/Program.cs +++ b/common/services/ASC.Webhooks.Service/Program.cs @@ -41,11 +41,20 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) => diHelper.TryAdd(); - services.AddHostedService(); - diHelper.TryAdd(); - services.AddHostedService(); - diHelper.TryAdd(); + diHelper.TryAdd(); + + services.AddHttpClient("webhook") + .SetHandlerLifetime(TimeSpan.FromMinutes(5)) + .AddPolicyHandler((s, request) => + { + var settings = s.GetRequiredService(); + + 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))); + }); }); builder.WebHost.ConfigureDefaultKestrel(); diff --git a/common/services/ASC.Webhooks.Service/Services/WorkerService.cs b/common/services/ASC.Webhooks.Service/Services/WorkerService.cs index 0ef5b6d39b..03d1a01504 100644 --- a/common/services/ASC.Webhooks.Service/Services/WorkerService.cs +++ b/common/services/ASC.Webhooks.Service/Services/WorkerService.cs @@ -29,26 +29,36 @@ namespace ASC.Webhooks.Service.Services; [Singletone] public class WorkerService : BackgroundService { - private readonly ILogger _logger; - private readonly ConcurrentQueue _queue; + private readonly ILogger _logger; private readonly int? _threadCount = 10; private readonly WebhookSender _webhookSender; - private readonly TimeSpan _waitingPeriod; + private readonly TimeSpan _waitingPeriod; + private readonly ConcurrentQueue _queue; + private readonly ICacheNotify _webhookNotify; - public WorkerService(WebhookSender webhookSender, + public WorkerService( + ICacheNotify webhookNotify, + WebhookSender webhookSender, ILogger logger, - BuildQueueService buildQueueService, Settings settings) - { - _logger = logger; + { + _webhookNotify = webhookNotify; + _queue = new ConcurrentQueue(); + _logger = logger; _webhookSender = webhookSender; - _queue = buildQueueService.Queue; _threadCount = settings.ThreadCount; _waitingPeriod = TimeSpan.FromSeconds(5); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { + { + _webhookNotify.Subscribe(_queue.Enqueue, CacheNotifyAction.Update); + + stoppingToken.Register(() => + { + _webhookNotify.Unsubscribe(CacheNotifyAction.Update); + }); + while (!stoppingToken.IsCancellationRequested) { var queueSize = _queue.Count; @@ -62,7 +72,7 @@ public class WorkerService : BackgroundService continue; } - var tasks = new List(); + var tasks = new List(queueSize); var counter = 0; for (var i = 0; i < queueSize; i++) @@ -82,13 +92,13 @@ public class WorkerService : BackgroundService if (counter >= _threadCount) { - Task.WaitAll(tasks.ToArray()); + await Task.WhenAll(tasks); tasks.Clear(); counter = 0; } - } - - Task.WaitAll(tasks.ToArray()); + } + + await Task.WhenAll(tasks); _logger.DebugProcedureFinish(); } } diff --git a/common/services/ASC.Webhooks.Service/Settings.cs b/common/services/ASC.Webhooks.Service/Settings.cs index 5ade284f35..0c3754160e 100644 --- a/common/services/ASC.Webhooks.Service/Settings.cs +++ b/common/services/ASC.Webhooks.Service/Settings.cs @@ -37,7 +37,7 @@ public class Settings { var cfg = configuration.GetSetting("webhooks"); RepeatCount = cfg.RepeatCount ?? 5; - ThreadCount = cfg.ThreadCount ?? 1; + ThreadCount = cfg.ThreadCount ?? 10; } public int? RepeatCount { get; } public int? ThreadCount { get; } diff --git a/common/services/ASC.Webhooks.Service/WebhookSender.cs b/common/services/ASC.Webhooks.Service/WebhookSender.cs index f67d750a7e..2a0ad3284e 100644 --- a/common/services/ASC.Webhooks.Service/WebhookSender.cs +++ b/common/services/ASC.Webhooks.Service/WebhookSender.cs @@ -24,6 +24,8 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +using System.Text.Json.Serialization; + namespace ASC.Webhooks.Service; [Singletone] @@ -31,15 +33,19 @@ public class WebhookSender { private readonly IHttpClientFactory _clientFactory; private readonly ILogger _log; - public int? RepeatCount { get; init; } - private readonly IServiceScopeFactory _scopeFactory; + private readonly IServiceScopeFactory _scopeFactory; + private readonly JsonSerializerOptions _jsonSerializerOptions; - public WebhookSender(ILoggerProvider options, IServiceScopeFactory scopeFactory, Settings settings, IHttpClientFactory clientFactory) + public WebhookSender(ILoggerProvider options, IServiceScopeFactory scopeFactory, IHttpClientFactory clientFactory) { _log = options.CreateLogger("ASC.Webhooks.Core"); _scopeFactory = scopeFactory; - RepeatCount = settings.RepeatCount; - _clientFactory = clientFactory; + _clientFactory = clientFactory; + _jsonSerializerOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + IgnoreReadOnlyProperties = true + }; } public async Task Send(WebhookRequest webhookRequest, CancellationToken cancellationToken) @@ -47,82 +53,65 @@ public class WebhookSender using var scope = _scopeFactory.CreateScope(); var dbWorker = scope.ServiceProvider.GetRequiredService(); - var entry = dbWorker.ReadFromJournal(webhookRequest.Id); - var id = entry.Id; - var requestURI = entry.Uri; - var secretKey = entry.SecretKey; - var data = entry.Payload; - - var response = new HttpResponseMessage(); - var request = new HttpRequestMessage(); - - for (var i = 0; i < RepeatCount; i++) - { - try - { - request = new HttpRequestMessage(HttpMethod.Post, requestURI); - request.Headers.Add("Accept", "*/*"); - request.Headers.Add("Secret", "SHA256=" + GetSecretHash(secretKey, data)); - - request.Content = new StringContent( - data, - Encoding.UTF8, - "application/json"); - - var httpClient = _clientFactory.CreateClient(); - response = await httpClient.SendAsync(request, cancellationToken); - - if (response.IsSuccessStatusCode) - { - UpdateDb(dbWorker, id, response, request, ProcessStatus.Success); - _log.DebugResponse(response); - break; - } - else if (i == RepeatCount - 1) - { - UpdateDb(dbWorker, id, response, request, ProcessStatus.Failed); - _log.DebugResponse(response); - } - } - catch (Exception ex) - { - if (i == RepeatCount - 1) - { - UpdateDb(dbWorker, id, response, request, ProcessStatus.Failed); - } - - _log.ErrorWithException(ex); - continue; - } + var entry = await dbWorker.ReadJournal(webhookRequest.Id); + + var status = 0; + string responsePayload = null; + string responseHeaders = null; + string requestHeaders = null; + var delivery = DateTime.MinValue; + + try + { + var httpClient = _clientFactory.CreateClient("webhook"); + var request = new HttpRequestMessage(HttpMethod.Post, entry.Config.Uri) + { + Content = new StringContent(entry.RequestPayload, Encoding.UTF8, "application/json") + }; + + request.Headers.Add("Accept", "*/*"); + request.Headers.Add("Secret", "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); + + 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 (HttpRequestException e) + { + if (e.StatusCode.HasValue) + { + status = (int)e.StatusCode.Value; + } + responsePayload = e.Message; + delivery = DateTime.UtcNow; + + _log.ErrorWithException(e); + } + catch (Exception e) + { + _log.ErrorWithException(e); + } + + if (delivery != DateTime.MinValue) + { + await dbWorker.UpdateWebhookJournal(entry.Id, status, delivery, requestHeaders, responsePayload, responseHeaders); } } private string GetSecretHash(string secretKey, string body) - { - string computedSignature; + { var secretBytes = Encoding.UTF8.GetBytes(secretKey); using (var hasher = new HMACSHA256(secretBytes)) { var data = Encoding.UTF8.GetBytes(body); - computedSignature = BitConverter.ToString(hasher.ComputeHash(data)); + return BitConverter.ToString(hasher.ComputeHash(data)); } - - return computedSignature; - } - - private void UpdateDb(DbWorker dbWorker, int id, HttpResponseMessage response, HttpRequestMessage request, ProcessStatus status) - { - var responseHeaders = JsonSerializer.Serialize(response.Headers.ToDictionary(r => r.Key, v => v.Value)); - var requestHeaders = JsonSerializer.Serialize(request.Headers.ToDictionary(r => r.Key, v => v.Value)); - string responsePayload; - - using (var streamReader = new StreamReader(response.Content.ReadAsStream())) - { - var responseContent = streamReader.ReadToEnd(); - responsePayload = JsonSerializer.Serialize(responseContent); - } - - dbWorker.UpdateWebhookJournal(id, status, responsePayload, responseHeaders, requestHeaders); } } \ No newline at end of file diff --git a/config/appsettings.json b/config/appsettings.json index 5e90bdae83..8c59a90629 100644 --- a/config/appsettings.json +++ b/config/appsettings.json @@ -34,6 +34,9 @@ "hosting": { "intervalCheckRegisterInstanceInSeconds": "1", "timeUntilUnregisterInSeconds": "15" + }, + "oidc": { + "authority" : "" } }, "license": { @@ -50,7 +53,7 @@ "enabled": "true" }, "version": { - "number": "11.5.0", + "number": "1.0.0", "release": { "date": "", "sign": "" @@ -92,8 +95,7 @@ "viewed-media": [ ".aac", ".flac", ".m4a", ".mp3", ".oga", ".ogg", ".wav", ".f4v", ".m4v", ".mov", ".mp4", ".ogv", ".webm" ], "index": [ ".pptx", ".xlsx", ".docx" ], "oform": { - "url": "https://cmsoforms.onlyoffice.com/api/oforms?populate=*&locale=all", - "period": 60, + "url": "https://cmsoforms.onlyoffice.com/api/oforms/", "ext": ".oform" } }, @@ -159,9 +161,14 @@ } }, "firebase": { + "projectId": "", + "privateKeyId": "", + "privateKey": "", + "clientEmail": "", + "clientId": "", + "x509CertUrl": "", "apiKey": "", "authDomain": "", - "projectId": "", "storageBucket": "", "messagingSenderId": "", "appId": "", diff --git a/config/storage.json b/config/storage.json index 7f07aaa046..7354eb00a5 100644 --- a/config/storage.json +++ b/config/storage.json @@ -76,10 +76,10 @@ "domain": [ { "name": "logos_temp", + "visible": false, "data": "00000000-0000-0000-0000-000000000000", "type": "disc", - "path": "$STORAGE_ROOT\\Products\\Files\\logos\\{0}\\temp", - "expires": "0:10:0" + "path": "$STORAGE_ROOT\\Products\\Files\\logos\\{0}\\temp" } ] }, diff --git a/migrations/mysql/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.Designer.cs b/migrations/mysql/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.Designer.cs new file mode 100644 index 0000000000..b0fece1905 --- /dev/null +++ b/migrations/mysql/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.Designer.cs @@ -0,0 +1,69 @@ +// +using System; +using ASC.Core.Common.EF.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ASC.Migrations.MySql.Migrations.FirebaseDb +{ + [DbContext(typeof(FirebaseDbContext))] + [Migration("20220823165923_FirebaseDbContextMigrate")] + partial class FirebaseDbContextMigrate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("ASC.Core.Common.EF.FireBaseUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Application") + .HasColumnType("varchar(20)") + .HasColumnName("application") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("FirebaseDeviceToken") + .HasColumnType("varchar(255)") + .HasColumnName("firebase_device_token") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("IsSubscribed") + .HasColumnType("tinyint(1)") + .HasColumnName("is_subscribed"); + + b.Property("TenantId") + .HasColumnType("int") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(36)") + .HasColumnName("user_id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("TenantId", "UserId") + .HasDatabaseName("user_id"); + + b.ToTable("firebase_users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/migrations/mysql/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.cs b/migrations/mysql/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.cs new file mode 100644 index 0000000000..1fcdf490fb --- /dev/null +++ b/migrations/mysql/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ASC.Migrations.MySql.Migrations.FirebaseDb +{ + public partial class FirebaseDbContextMigrate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "firebase_users", + columns: table => new + { + id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + user_id = table.Column(type: "varchar(36)", nullable: false, collation: "utf8_general_ci") + .Annotation("MySql:CharSet", "utf8"), + tenant_id = table.Column(type: "int", nullable: false), + firebase_device_token = table.Column(type: "varchar(255)", nullable: true, collation: "utf8_general_ci") + .Annotation("MySql:CharSet", "utf8"), + application = table.Column(type: "varchar(20)", nullable: true, collation: "utf8_general_ci") + .Annotation("MySql:CharSet", "utf8"), + is_subscribed = table.Column(type: "tinyint(1)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PRIMARY", x => x.id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "user_id", + table: "firebase_users", + columns: new[] { "tenant_id", "user_id" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "firebase_users"); + } + } +} diff --git a/migrations/mysql/FirebaseDbContext/FirebaseDbContextModelSnapshot.cs b/migrations/mysql/FirebaseDbContext/FirebaseDbContextModelSnapshot.cs new file mode 100644 index 0000000000..4e26247259 --- /dev/null +++ b/migrations/mysql/FirebaseDbContext/FirebaseDbContextModelSnapshot.cs @@ -0,0 +1,67 @@ +// +using System; +using ASC.Core.Common.EF.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ASC.Migrations.MySql.Migrations.FirebaseDb +{ + [DbContext(typeof(FirebaseDbContext))] + partial class FirebaseDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("ASC.Core.Common.EF.FireBaseUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Application") + .HasColumnType("varchar(20)") + .HasColumnName("application") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("FirebaseDeviceToken") + .HasColumnType("varchar(255)") + .HasColumnName("firebase_device_token") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("IsSubscribed") + .HasColumnType("tinyint(1)") + .HasColumnName("is_subscribed"); + + b.Property("TenantId") + .HasColumnType("int") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(36)") + .HasColumnName("user_id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("TenantId", "UserId") + .HasDatabaseName("user_id"); + + b.ToTable("firebase_users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/migrations/mysql/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs b/migrations/mysql/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs new file mode 100644 index 0000000000..518e40c72f --- /dev/null +++ b/migrations/mysql/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs @@ -0,0 +1,163 @@ +// +using System; +using ASC.Webhooks.Core.EF.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ASC.Migrations.MySql.Migrations.WebhooksDb +{ + [DbContext(typeof(WebhooksDbContext))] + [Migration("20220818144209_WebhooksDbContext_Upgrade1")] + partial class WebhooksDbContext_Upgrade1 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasColumnName("enabled") + .HasDefaultValueSql("'1'"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasColumnName("name"); + + b.Property("SecretKey") + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasColumnName("secret_key") + .HasDefaultValueSql("''"); + + b.Property("TenantId") + .HasColumnType("int unsigned") + .HasColumnName("tenant_id"); + + b.Property("Uri") + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasColumnName("uri") + .HasDefaultValueSql("''"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("TenantId") + .HasDatabaseName("tenant_id"); + + b.ToTable("webhooks_config", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + b.Property("ConfigId") + .HasColumnType("int") + .HasColumnName("config_id"); + + b.Property("CreationTime") + .HasColumnType("datetime") + .HasColumnName("creation_time"); + + b.Property("Delivery") + .HasColumnType("datetime") + .HasColumnName("delivery"); + + b.Property("Method") + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("method"); + + b.Property("RequestHeaders") + .HasColumnType("json") + .HasColumnName("request_headers"); + + b.Property("RequestPayload") + .IsRequired() + .HasColumnType("text") + .HasColumnName("request_payload") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("ResponseHeaders") + .HasColumnType("json") + .HasColumnName("response_headers"); + + b.Property("ResponsePayload") + .HasColumnType("text") + .HasColumnName("response_payload") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("Route") + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("route"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("int unsigned") + .HasColumnName("tenant_id"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("varchar(36)") + .HasColumnName("uid") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("ConfigId"); + + b.HasIndex("TenantId") + .HasDatabaseName("tenant_id"); + + b.ToTable("webhooks_logs", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b => + { + b.HasOne("ASC.Webhooks.Core.EF.Model.WebhooksConfig", "Config") + .WithMany() + .HasForeignKey("ConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Config"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/migrations/mysql/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.cs b/migrations/mysql/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.cs new file mode 100644 index 0000000000..2cc14406df --- /dev/null +++ b/migrations/mysql/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.cs @@ -0,0 +1,151 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ASC.Migrations.MySql.Migrations.WebhooksDb +{ + public partial class WebhooksDbContext_Upgrade1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "config_id", + table: "webhooks_config", + newName: "id"); + + migrationBuilder.UpdateData( + table: "webhooks_logs", + keyColumn: "uid", + keyValue: null, + column: "uid", + value: ""); + + migrationBuilder.AlterColumn( + name: "uid", + table: "webhooks_logs", + type: "varchar(36)", + nullable: false, + collation: "utf8_general_ci", + oldClrType: typeof(string), + oldType: "varchar(50)", + oldMaxLength: 50, + oldNullable: true) + .Annotation("MySql:CharSet", "utf8") + .OldAnnotation("MySql:CharSet", "utf8"); + + migrationBuilder.AlterColumn( + name: "status", + table: "webhooks_logs", + type: "int", + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(50)", + oldMaxLength: 50) + .OldAnnotation("MySql:CharSet", "utf8"); + + migrationBuilder.AddColumn( + name: "delivery", + table: "webhooks_logs", + type: "datetime", + nullable: true); + + migrationBuilder.AddColumn( + name: "enabled", + table: "webhooks_config", + type: "tinyint(1)", + nullable: false, + defaultValueSql: "'1'"); + + migrationBuilder.AddColumn( + name: "name", + table: "webhooks_config", + type: "varchar(50)", + maxLength: 50, + nullable: false, + defaultValue: "") + .Annotation("MySql:CharSet", "utf8"); + + 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_config", + column: "tenant_id"); + + migrationBuilder.AddForeignKey( + name: "FK_webhooks_logs_webhooks_config_config_id", + table: "webhooks_logs", + column: "config_id", + principalTable: "webhooks_config", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_webhooks_logs_webhooks_config_config_id", + table: "webhooks_logs"); + + migrationBuilder.DropIndex( + name: "IX_webhooks_logs_config_id", + table: "webhooks_logs"); + + migrationBuilder.DropIndex( + name: "tenant_id", + table: "webhooks_logs"); + + migrationBuilder.DropIndex( + name: "tenant_id", + table: "webhooks_config"); + + migrationBuilder.DropColumn( + name: "delivery", + table: "webhooks_logs"); + + migrationBuilder.DropColumn( + name: "enabled", + table: "webhooks_config"); + + migrationBuilder.DropColumn( + name: "name", + table: "webhooks_config"); + + migrationBuilder.RenameColumn( + name: "id", + table: "webhooks_config", + newName: "config_id"); + + migrationBuilder.AlterColumn( + name: "uid", + table: "webhooks_logs", + type: "varchar(50)", + maxLength: 50, + nullable: true, + oldClrType: typeof(string), + oldType: "varchar(36)", + oldCollation: "utf8_general_ci") + .Annotation("MySql:CharSet", "utf8") + .OldAnnotation("MySql:CharSet", "utf8"); + + migrationBuilder.AlterColumn( + name: "status", + table: "webhooks_logs", + type: "varchar(50)", + maxLength: 50, + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:CharSet", "utf8"); + } + } +} diff --git a/migrations/mysql/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs b/migrations/mysql/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs index 53e7ba2bfd..13a232a63a 100644 --- a/migrations/mysql/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs +++ b/migrations/mysql/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs @@ -16,15 +16,27 @@ namespace ASC.Migrations.MySql.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("ProductVersion", "6.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b => { - b.Property("ConfigId") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") - .HasColumnName("config_id"); + .HasColumnName("id"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasColumnName("enabled") + .HasDefaultValueSql("'1'"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasColumnName("name"); b.Property("SecretKey") .ValueGeneratedOnAdd() @@ -44,9 +56,12 @@ namespace ASC.Migrations.MySql.Migrations .HasColumnName("uri") .HasDefaultValueSql("''"); - b.HasKey("ConfigId") + b.HasKey("Id") .HasName("PRIMARY"); + b.HasIndex("TenantId") + .HasDatabaseName("tenant_id"); + b.ToTable("webhooks_config", (string)null); b.HasAnnotation("MySql:CharSet", "utf8"); @@ -67,10 +82,14 @@ namespace ASC.Migrations.MySql.Migrations .HasColumnType("datetime") .HasColumnName("creation_time"); - b.Property("Event") + b.Property("Delivery") + .HasColumnType("datetime") + .HasColumnName("delivery"); + + b.Property("Method") .HasMaxLength(100) .HasColumnType("varchar(100)") - .HasColumnName("event"); + .HasColumnName("method"); b.Property("RequestHeaders") .HasColumnType("json") @@ -78,21 +97,28 @@ namespace ASC.Migrations.MySql.Migrations b.Property("RequestPayload") .IsRequired() - .HasColumnType("json") - .HasColumnName("request_payload"); + .HasColumnType("text") + .HasColumnName("request_payload") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); b.Property("ResponseHeaders") .HasColumnType("json") .HasColumnName("response_headers"); b.Property("ResponsePayload") - .HasColumnType("json") - .HasColumnName("response_payload"); + .HasColumnType("text") + .HasColumnName("response_payload") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); - b.Property("Status") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)") + b.Property("Route") + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("route"); + + b.Property("Status") + .HasColumnType("int") .HasColumnName("status"); b.Property("TenantId") @@ -100,17 +126,35 @@ namespace ASC.Migrations.MySql.Migrations .HasColumnName("tenant_id"); b.Property("Uid") - .HasMaxLength(50) - .HasColumnType("varchar(50)") - .HasColumnName("uid"); + .IsRequired() + .HasColumnType("varchar(36)") + .HasColumnName("uid") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); b.HasKey("Id") .HasName("PRIMARY"); + b.HasIndex("ConfigId"); + + b.HasIndex("TenantId") + .HasDatabaseName("tenant_id"); + b.ToTable("webhooks_logs", (string)null); b.HasAnnotation("MySql:CharSet", "utf8"); }); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b => + { + b.HasOne("ASC.Webhooks.Core.EF.Model.WebhooksConfig", "Config") + .WithMany() + .HasForeignKey("ConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Config"); + }); #pragma warning restore 612, 618 } } diff --git a/migrations/postgre/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.Designer.cs b/migrations/postgre/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.Designer.cs new file mode 100644 index 0000000000..7ea3f064f2 --- /dev/null +++ b/migrations/postgre/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.Designer.cs @@ -0,0 +1,68 @@ +// +using System; +using ASC.Core.Common.EF.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ASC.Migrations.PostgreSql.Migrations.FirebaseDb +{ + [DbContext(typeof(FirebaseDbContext))] + [Migration("20220823165923_FirebaseDbContextMigrate")] + partial class FirebaseDbContextMigrate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("ASC.Core.Common.EF.FireBaseUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Application") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("application"); + + b.Property("FirebaseDeviceToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("firebase_device_token"); + + b.Property("IsSubscribed") + .HasColumnType("boolean") + .HasColumnName("is_subscribed"); + + b.Property("TenantId") + .HasColumnType("integer") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasMaxLength(36) + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("firebase_users_pkey"); + + b.HasIndex("TenantId", "UserId") + .HasDatabaseName("user_id"); + + b.ToTable("firebase_users", "onlyoffice"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/migrations/postgre/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.cs b/migrations/postgre/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.cs new file mode 100644 index 0000000000..75aec99be6 --- /dev/null +++ b/migrations/postgre/FirebaseDbContext/20220823165923_FirebaseDbContextMigrate.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ASC.Migrations.PostgreSql.Migrations.FirebaseDb +{ + public partial class FirebaseDbContextMigrate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "onlyoffice"); + + migrationBuilder.CreateTable( + name: "firebase_users", + schema: "onlyoffice", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(type: "uuid", maxLength: 36, nullable: false), + tenant_id = table.Column(type: "integer", nullable: false), + firebase_device_token = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + application = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + is_subscribed = table.Column(type: "boolean", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("firebase_users_pkey", x => x.id); + }); + + migrationBuilder.CreateIndex( + name: "user_id", + schema: "onlyoffice", + table: "firebase_users", + columns: new[] { "tenant_id", "user_id" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "firebase_users", + schema: "onlyoffice"); + } + } +} diff --git a/migrations/postgre/FirebaseDbContext/FirebaseDbContextModelSnapshot.cs b/migrations/postgre/FirebaseDbContext/FirebaseDbContextModelSnapshot.cs new file mode 100644 index 0000000000..5ba91d1723 --- /dev/null +++ b/migrations/postgre/FirebaseDbContext/FirebaseDbContextModelSnapshot.cs @@ -0,0 +1,66 @@ +// +using System; +using ASC.Core.Common.EF.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ASC.Migrations.PostgreSql.Migrations.FirebaseDb +{ + [DbContext(typeof(FirebaseDbContext))] + partial class FirebaseDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("ASC.Core.Common.EF.FireBaseUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Application") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("application"); + + b.Property("FirebaseDeviceToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("firebase_device_token"); + + b.Property("IsSubscribed") + .HasColumnType("boolean") + .HasColumnName("is_subscribed"); + + b.Property("TenantId") + .HasColumnType("integer") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasMaxLength(36) + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("firebase_users_pkey"); + + b.HasIndex("TenantId", "UserId") + .HasDatabaseName("user_id"); + + b.ToTable("firebase_users", "onlyoffice"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/migrations/postgre/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs b/migrations/postgre/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs new file mode 100644 index 0000000000..c94244b6b9 --- /dev/null +++ b/migrations/postgre/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.Designer.cs @@ -0,0 +1,158 @@ +// +using System; +using ASC.Webhooks.Core.EF.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb +{ + [DbContext(typeof(WebhooksDbContext))] + [Migration("20220818144209_WebhooksDbContext_Upgrade1")] + partial class WebhooksDbContext_Upgrade1 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasColumnName("enabled") + .HasDefaultValueSql("true"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("name"); + + b.Property("SecretKey") + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("secret_key") + .HasDefaultValueSql("''"); + + b.Property("TenantId") + .HasColumnType("int unsigned") + .HasColumnName("tenant_id"); + + b.Property("Uri") + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("uri") + .HasDefaultValueSql("''"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("TenantId") + .HasDatabaseName("tenant_id"); + + b.ToTable("webhooks_config", (string)null); + }); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ConfigId") + .HasColumnType("int") + .HasColumnName("config_id"); + + b.Property("CreationTime") + .HasColumnType("datetime") + .HasColumnName("creation_time"); + + b.Property("Delivery") + .HasColumnType("datetime") + .HasColumnName("delivery"); + + b.Property("Method") + .HasMaxLength(100) + .HasColumnType("varchar") + .HasColumnName("method"); + + b.Property("RequestHeaders") + .HasColumnType("json") + .HasColumnName("request_headers"); + + b.Property("RequestPayload") + .IsRequired() + .HasColumnType("text") + .HasColumnName("request_payload"); + + b.Property("ResponseHeaders") + .HasColumnType("json") + .HasColumnName("response_headers"); + + b.Property("ResponsePayload") + .HasColumnType("text") + .HasColumnName("response_payload"); + + b.Property("Route") + .HasMaxLength(100) + .HasColumnType("varchar") + .HasColumnName("route"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("int unsigned") + .HasColumnName("tenant_id"); + + b.Property("Uid") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar") + .HasColumnName("uid"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("ConfigId"); + + b.HasIndex("TenantId") + .HasDatabaseName("tenant_id"); + + b.ToTable("webhooks_logs", (string)null); + }); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b => + { + b.HasOne("ASC.Webhooks.Core.EF.Model.WebhooksConfig", "Config") + .WithMany() + .HasForeignKey("ConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Config"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/migrations/postgre/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.cs b/migrations/postgre/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.cs new file mode 100644 index 0000000000..75c35a3790 --- /dev/null +++ b/migrations/postgre/WebhooksDbContext/20220818144209_WebhooksDbContext_Upgrade1.cs @@ -0,0 +1,138 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb +{ + public partial class WebhooksDbContext_Upgrade1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "config_id", + table: "webhooks_config", + newName: "id"); + + migrationBuilder.AlterColumn( + name: "uid", + table: "webhooks_logs", + type: "varchar", + maxLength: 50, + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "varchar", + oldMaxLength: 50, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "status", + table: "webhooks_logs", + type: "int", + nullable: false, + oldClrType: typeof(string), + oldType: "varchar", + oldMaxLength: 50); + + migrationBuilder.AddColumn( + name: "delivery", + table: "webhooks_logs", + type: "datetime", + nullable: true); + + migrationBuilder.AddColumn( + name: "enabled", + table: "webhooks_config", + type: "boolean", + nullable: false, + defaultValueSql: "true"); + + migrationBuilder.AddColumn( + name: "name", + table: "webhooks_config", + type: "character varying(50)", + maxLength: 50, + nullable: false, + defaultValue: ""); + + 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_config", + column: "tenant_id"); + + migrationBuilder.AddForeignKey( + name: "FK_webhooks_logs_webhooks_config_config_id", + table: "webhooks_logs", + column: "config_id", + principalTable: "webhooks_config", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_webhooks_logs_webhooks_config_config_id", + table: "webhooks_logs"); + + migrationBuilder.DropIndex( + name: "IX_webhooks_logs_config_id", + table: "webhooks_logs"); + + migrationBuilder.DropIndex( + name: "tenant_id", + table: "webhooks_logs"); + + migrationBuilder.DropIndex( + name: "tenant_id", + table: "webhooks_config"); + + migrationBuilder.DropColumn( + name: "delivery", + table: "webhooks_logs"); + + migrationBuilder.DropColumn( + name: "enabled", + table: "webhooks_config"); + + migrationBuilder.DropColumn( + name: "name", + table: "webhooks_config"); + + migrationBuilder.RenameColumn( + name: "id", + table: "webhooks_config", + newName: "config_id"); + + migrationBuilder.AlterColumn( + name: "uid", + table: "webhooks_logs", + type: "varchar", + maxLength: 50, + nullable: true, + oldClrType: typeof(string), + oldType: "varchar", + oldMaxLength: 50); + + migrationBuilder.AlterColumn( + name: "status", + table: "webhooks_logs", + type: "varchar", + maxLength: 50, + nullable: false, + oldClrType: typeof(int), + oldType: "int"); + } + } +} diff --git a/migrations/postgre/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs b/migrations/postgre/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs index 07fbed37ca..0c5af441ed 100644 --- a/migrations/postgre/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs +++ b/migrations/postgre/WebhooksDbContext/WebhooksDbContextModelSnapshot.cs @@ -18,17 +18,29 @@ namespace ASC.Migrations.PostgreSql.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("ProductVersion", "6.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b => { - b.Property("ConfigId") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") - .HasColumnName("config_id") + .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasColumnName("enabled") + .HasDefaultValueSql("true"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("name"); + b.Property("SecretKey") .ValueGeneratedOnAdd() .HasMaxLength(50) @@ -47,9 +59,12 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnName("uri") .HasDefaultValueSql("''"); - b.HasKey("ConfigId") + b.HasKey("Id") .HasName("PRIMARY"); + b.HasIndex("TenantId") + .HasDatabaseName("tenant_id"); + b.ToTable("webhooks_config", (string)null); }); @@ -69,10 +84,14 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnType("datetime") .HasColumnName("creation_time"); - b.Property("Event") + b.Property("Delivery") + .HasColumnType("datetime") + .HasColumnName("delivery"); + + b.Property("Method") .HasMaxLength(100) .HasColumnType("varchar") - .HasColumnName("event"); + .HasColumnName("method"); b.Property("RequestHeaders") .HasColumnType("json") @@ -80,7 +99,7 @@ namespace ASC.Migrations.PostgreSql.Migrations b.Property("RequestPayload") .IsRequired() - .HasColumnType("json") + .HasColumnType("text") .HasColumnName("request_payload"); b.Property("ResponseHeaders") @@ -88,13 +107,16 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnName("response_headers"); b.Property("ResponsePayload") - .HasColumnType("json") + .HasColumnType("text") .HasColumnName("response_payload"); - b.Property("Status") - .IsRequired() - .HasMaxLength(50) + b.Property("Route") + .HasMaxLength(100) .HasColumnType("varchar") + .HasColumnName("route"); + + b.Property("Status") + .HasColumnType("int") .HasColumnName("status"); b.Property("TenantId") @@ -102,6 +124,7 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnName("tenant_id"); b.Property("Uid") + .IsRequired() .HasMaxLength(50) .HasColumnType("varchar") .HasColumnName("uid"); @@ -109,8 +132,24 @@ namespace ASC.Migrations.PostgreSql.Migrations b.HasKey("Id") .HasName("PRIMARY"); + b.HasIndex("ConfigId"); + + b.HasIndex("TenantId") + .HasDatabaseName("tenant_id"); + b.ToTable("webhooks_logs", (string)null); }); + + modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksLog", b => + { + b.HasOne("ASC.Webhooks.Core.EF.Model.WebhooksConfig", "Config") + .WithMany() + .HasForeignKey("ConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Config"); + }); #pragma warning restore 612, 618 } } diff --git a/packages/client/public/locales/az/NavMenu.json b/packages/client/public/locales/az/NavMenu.json index 6a149286da..667261331f 100644 --- a/packages/client/public/locales/az/NavMenu.json +++ b/packages/client/public/locales/az/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Girişi bərpa edin", - "RecoverContactEmailPlaceholder": "Əlaqə üçün e-poçt ünvanı", - "RecoverDescribeYourProblemPlaceholder": "Probleminizi təsvir edin", - "RecoverTextBody": "Cari hesabınızla giriş edə bilmirsinizsə və ya yeni istifadəçi kimi qeydiyyatdan keçmək istəyirsinizsə, o zaman portal administratoru ilə əlaqə saxlayın.", - "RecoverTitle": "Girişin bərpası", "TurnOnDesktopVersion": "Masaüstü versiyanı işə salın" } diff --git a/packages/client/public/locales/bg/NavMenu.json b/packages/client/public/locales/bg/NavMenu.json index 208c3774e9..2ff8922735 100644 --- a/packages/client/public/locales/bg/NavMenu.json +++ b/packages/client/public/locales/bg/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Поднови достъп", - "RecoverContactEmailPlaceholder": "Имейл за контакт", - "RecoverDescribeYourProblemPlaceholder": "Опишете проблема си", - "RecoverTextBody": "Ако не можете да се впишете със съществуващия си профил или искате да се регистрирате като нов потребител, свържете се с администратора на портала. ", - "RecoverTitle": "Възстановяване на достъп", "TurnOnDesktopVersion": "Превключете на настолна версия" } diff --git a/packages/client/public/locales/cs/NavMenu.json b/packages/client/public/locales/cs/NavMenu.json index 6f5550b01b..aa565e68dc 100644 --- a/packages/client/public/locales/cs/NavMenu.json +++ b/packages/client/public/locales/cs/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Obnovit přístup", - "RecoverContactEmailPlaceholder": "Kontaktní emailová adresa", - "RecoverDescribeYourProblemPlaceholder": "Popište svůj problém", - "RecoverTextBody": "Pokud se nemůžete přihlásit pomocí stávajícího účtu nebo chcete být zaregistrováni jako nový uživatel, kontaktujte správce portálu. ", - "RecoverTitle": "Obnovení přístupu", "TurnOnDesktopVersion": "Zapnout verzi pro počítač" } diff --git a/packages/client/public/locales/de/NavMenu.json b/packages/client/public/locales/de/NavMenu.json index 085a2f4164..aafc5a0bf7 100644 --- a/packages/client/public/locales/de/NavMenu.json +++ b/packages/client/public/locales/de/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Zugriff wiederherstellen", - "RecoverContactEmailPlaceholder": "E-Mail-Adresse", - "RecoverDescribeYourProblemPlaceholder": "Bitte Problem beschreiben", - "RecoverTextBody": "Wenn Sie sich mit Ihren Anmeldeinformationen nicht einloggen können oder ein neues Profil erstellen möchten, wenden Sie sich an den Administrator des Portals.", - "RecoverTitle": "Zugriffswiederherstellung", "TurnOnDesktopVersion": "Desktopversion anzeigen" } diff --git a/packages/client/public/locales/el/NavMenu.json b/packages/client/public/locales/el/NavMenu.json index 56ca586135..b089c0ba51 100644 --- a/packages/client/public/locales/el/NavMenu.json +++ b/packages/client/public/locales/el/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Ανάκτηση πρόσβασης", - "RecoverContactEmailPlaceholder": "Email επικοινωνίας", - "RecoverDescribeYourProblemPlaceholder": "Περιγράψτε το πρόβλημά σας", - "RecoverTextBody": "Εάν δεν μπορείτε να συνδεθείτε με τον υπάρχοντα λογαριασμό σας ή θέλετε να εγγραφείτε ως νέος χρήστης, επικοινωνήστε με τον διαχειριστή της πύλης.", - "RecoverTitle": "Ανάκτηση πρόσβασης", "TurnOnDesktopVersion": "Ενεργοποιήστε την έκδοση για υπολογιστές" } diff --git a/packages/client/public/locales/en/NavMenu.json b/packages/client/public/locales/en/NavMenu.json index c66556bf46..3c8edc6363 100644 --- a/packages/client/public/locales/en/NavMenu.json +++ b/packages/client/public/locales/en/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Recover access", - "RecoverContactEmailPlaceholder": "Contact email", - "RecoverDescribeYourProblemPlaceholder": "Describe your problem", - "RecoverTextBody": "If you can't log in with your existing account or want to be registered as a new user, contact the portal administrator.", - "RecoverTitle": "Access recovery", "TurnOnDesktopVersion": "Turn on desktop version" } diff --git a/packages/client/public/locales/es/NavMenu.json b/packages/client/public/locales/es/NavMenu.json index 1d42683841..d6fd16f3b5 100644 --- a/packages/client/public/locales/es/NavMenu.json +++ b/packages/client/public/locales/es/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Recuperar acceso", - "RecoverContactEmailPlaceholder": "E-mail de contacto", - "RecoverDescribeYourProblemPlaceholder": "Describa su problema", - "RecoverTextBody": "Si no puede conectarse con su cuenta actual o quiere registrarse como nuevo usuario, póngase en contacto con el administrador del portal. ", - "RecoverTitle": "Recuperación de acceso", "TurnOnDesktopVersion": "Activar la versión de escritorio" } diff --git a/packages/client/public/locales/fi/NavMenu.json b/packages/client/public/locales/fi/NavMenu.json index bb64de5add..87556e82b1 100644 --- a/packages/client/public/locales/fi/NavMenu.json +++ b/packages/client/public/locales/fi/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Palauta käyttöoikeus", - "RecoverContactEmailPlaceholder": "Sähköposti yhteydenottoa varten", - "RecoverDescribeYourProblemPlaceholder": "Kuvaile ongelmasi", - "RecoverTextBody": "Jos et voi kirjautua sisään nykyisellä tililläsi tai haluat rekisteröityä uutena käyttäjänä, ota yhteyttä portaalin järjestelmänvalvojaan. ", - "RecoverTitle": "Käytä palautusta", "TurnOnDesktopVersion": "Ota työpöytäversio käyttöön" } diff --git a/packages/client/public/locales/fr/NavMenu.json b/packages/client/public/locales/fr/NavMenu.json index 315f0f4b74..e89d525760 100644 --- a/packages/client/public/locales/fr/NavMenu.json +++ b/packages/client/public/locales/fr/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Récupérer l'accès", - "RecoverContactEmailPlaceholder": "E-mail de contact", - "RecoverDescribeYourProblemPlaceholder": "Décrivez votre problème", - "RecoverTextBody": "Si vous ne pouvez pas vous connecter avec votre compte existant ou que vous souhaitez en créer un nouveau, merci de contacter l'administrateur du portail.", - "RecoverTitle": "Récupération d'accès", "TurnOnDesktopVersion": "Activer l'application de bureau" } diff --git a/packages/client/public/locales/hy-AM/NavMenu.json b/packages/client/public/locales/hy-AM/NavMenu.json index f601d055ba..cdd6fec55c 100644 --- a/packages/client/public/locales/hy-AM/NavMenu.json +++ b/packages/client/public/locales/hy-AM/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Վերականգնել մատչումը", - "RecoverContactEmailPlaceholder": "Կոնտակտային էլ. փոստ", - "RecoverDescribeYourProblemPlaceholder": "Նկարագրեք Ձեր խնդիրը", - "RecoverTextBody": "Եթե ​​Դուք չեք կարող մուտք գործել Ձեր գոյություն ունեցող հաշիվ կամ ցանկանում եք գրանցվել որպես նոր օգտվող, կապվեք կայքէջի ադմինիստրատորի հետ:", - "RecoverTitle": "Մատչման վերականգնում", "TurnOnDesktopVersion": "Միացնել աշխատասեղանի տարբերակը" } diff --git a/packages/client/public/locales/it/NavMenu.json b/packages/client/public/locales/it/NavMenu.json index 0b2b8385ea..d62af915fc 100644 --- a/packages/client/public/locales/it/NavMenu.json +++ b/packages/client/public/locales/it/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Rimuovi accesso", - "RecoverContactEmailPlaceholder": "Email di contatto", - "RecoverDescribeYourProblemPlaceholder": "Descrivi il tuo problema", - "RecoverTextBody": "Se non riesci ad accedere con il tuo account esistente o desideri essere registrato come un nuovo utente, contatta l'amministratore del portale.", - "RecoverTitle": "Ripristino dell'accesso", "TurnOnDesktopVersion": "‎Attivare la versione desktop‎" } diff --git a/packages/client/public/locales/ja/NavMenu.json b/packages/client/public/locales/ja/NavMenu.json index 2287c1e8c5..7b9672929c 100644 --- a/packages/client/public/locales/ja/NavMenu.json +++ b/packages/client/public/locales/ja/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "アクセス回復", - "RecoverContactEmailPlaceholder": "連絡先メール", - "RecoverDescribeYourProblemPlaceholder": "ご質問を説明ください", - "RecoverTextBody": "既存のアカウントでログインできない場合や、新規ユーザーとして登録したい場合は、ポータル管理者にお問い合わせください。 ", - "RecoverTitle": "アクセス回復機能", "TurnOnDesktopVersion": "デスクトップ版を起動する" } diff --git a/packages/client/public/locales/ko/NavMenu.json b/packages/client/public/locales/ko/NavMenu.json index faf134e780..b84432e1a8 100644 --- a/packages/client/public/locales/ko/NavMenu.json +++ b/packages/client/public/locales/ko/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "액세스 복구", - "RecoverContactEmailPlaceholder": "연락처 이메일", - "RecoverDescribeYourProblemPlaceholder": "겪고 계신 문제를 설명해주세요", - "RecoverTextBody": "기존 계정으로 로그인하실 수 없거나 새로운 사용자로 등록하시길 원하시는 경우, 포털 관리자에게 문의하세요. ", - "RecoverTitle": "액세스 복원", "TurnOnDesktopVersion": "데스크탑 버전 켜기" } diff --git a/packages/client/public/locales/lo/NavMenu.json b/packages/client/public/locales/lo/NavMenu.json index 3d2e7bea46..4d9f39b893 100644 --- a/packages/client/public/locales/lo/NavMenu.json +++ b/packages/client/public/locales/lo/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "ກູ້ຄືນການເຂົ້າເຖິງ", - "RecoverContactEmailPlaceholder": "ອີເມວຕິດຕໍ່", - "RecoverDescribeYourProblemPlaceholder": "ອະທິບາຍບັນຫາຂອງທ່ານ", - "RecoverTextBody": "ຖ້າທ່ານບໍ່ສາມາດເຂົ້າສູ່ລະບົບໄດ້ຫຼືຕ້ອງການລົງທະບຽນໃຫມ່, ໃຫ້ທ່ານຕິດຕໍ່ຜູ້ດູແລລະບົບ portal ", - "RecoverTitle": "ການເຂົ້າເຖິງການກູ້ຄືນ", "TurnOnDesktopVersion": "ເປີດທາງ desktop" } diff --git a/packages/client/public/locales/lv/NavMenu.json b/packages/client/public/locales/lv/NavMenu.json index 8d9532a50b..41c35f90b0 100644 --- a/packages/client/public/locales/lv/NavMenu.json +++ b/packages/client/public/locales/lv/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Atgūt piekļuvi", - "RecoverContactEmailPlaceholder": "E-pasts saziņai", - "RecoverDescribeYourProblemPlaceholder": "Aprakstiet savu problēmu", - "RecoverTextBody": "Ja nevarat pieteikties ar savu esošo kontu vai vēlaties reģistrēties kā jauns lietotājs, sazinieties ar portāla administratoru. ", - "RecoverTitle": "Piekļuves atkopšana", "TurnOnDesktopVersion": "Ieslēgt darbvirsmas versiju" } diff --git a/packages/client/public/locales/nl/NavMenu.json b/packages/client/public/locales/nl/NavMenu.json index 5cd0184031..cff74b7e68 100644 --- a/packages/client/public/locales/nl/NavMenu.json +++ b/packages/client/public/locales/nl/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Toegang herstellen", - "RecoverContactEmailPlaceholder": "Contact e-mail", - "RecoverDescribeYourProblemPlaceholder": "Beschrijf uw probleem", - "RecoverTextBody": "Als u zich niet kunt aanmelden met uw bestaande account of als nieuwe gebruiker wilt worden geregistreerd, neem dan contact op met de portaalbeheerder.", - "RecoverTitle": "Toegang herstel", "TurnOnDesktopVersion": "Schakel desktop versie in" } diff --git a/packages/client/public/locales/pl/NavMenu.json b/packages/client/public/locales/pl/NavMenu.json index 156ded901a..e62eae84fe 100644 --- a/packages/client/public/locales/pl/NavMenu.json +++ b/packages/client/public/locales/pl/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Odzyskaj dostęp", - "RecoverContactEmailPlaceholder": "E-mail kontaktowy", - "RecoverDescribeYourProblemPlaceholder": "Opisz swój problem", - "RecoverTextBody": "Jeśli nie możesz się zalogować za pomocą istniejącego konta, lub chcesz zarejestrować się jako nowy użytkownik, skontaktuj się z administratorem portalu. ", - "RecoverTitle": "Odzyskiwanie dostępu", "TurnOnDesktopVersion": "Włącz wersję na komputer stacjonarny" } diff --git a/packages/client/public/locales/pt-BR/NavMenu.json b/packages/client/public/locales/pt-BR/NavMenu.json index 81ebead684..af04c74176 100644 --- a/packages/client/public/locales/pt-BR/NavMenu.json +++ b/packages/client/public/locales/pt-BR/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Recuperar o acesso", - "RecoverContactEmailPlaceholder": "E-mail de contato", - "RecoverDescribeYourProblemPlaceholder": "Descreva seu problema", - "RecoverTextBody": "Se você não conseguir entrar com sua conta existente ou quiser ser registrado como um novo usuário, entre em contato com o administrador do portal. ", - "RecoverTitle": "Recuperação de acesso", "TurnOnDesktopVersion": "Ligar a versão desktop" } diff --git a/packages/client/public/locales/pt/NavMenu.json b/packages/client/public/locales/pt/NavMenu.json index f196961311..9e3abb2dc2 100644 --- a/packages/client/public/locales/pt/NavMenu.json +++ b/packages/client/public/locales/pt/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Recuperar o acesso", - "RecoverContactEmailPlaceholder": "Email de contacto", - "RecoverDescribeYourProblemPlaceholder": "Descreva o seu problema", - "RecoverTextBody": "Se não consegue iniciar sessão com a sua conta ou se quiser registar-se como um novo utilizador, contacte o administrador do portal.", - "RecoverTitle": "Recuperação de acesso", "TurnOnDesktopVersion": "Ativar a versão para computador" } diff --git a/packages/client/public/locales/ro/NavMenu.json b/packages/client/public/locales/ro/NavMenu.json index f3caf6bc6f..d7503b8f6e 100644 --- a/packages/client/public/locales/ro/NavMenu.json +++ b/packages/client/public/locales/ro/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Recuperarea accesului la cont", - "RecoverContactEmailPlaceholder": "E-mail de contact", - "RecoverDescribeYourProblemPlaceholder": "Descrieți problema", - "RecoverTextBody": "Dacă nu puteți să vă conectați la contul dvs curent sau doriți să vă înregistrați din nou, contactați administratorul portalului. ", - "RecoverTitle": "Restabilirea accesului", "TurnOnDesktopVersion": "Lansați versiunea desktop" } diff --git a/packages/client/public/locales/ru/NavMenu.json b/packages/client/public/locales/ru/NavMenu.json index 1df4d90ed5..7362c13b09 100644 --- a/packages/client/public/locales/ru/NavMenu.json +++ b/packages/client/public/locales/ru/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Доступ к порталу", - "RecoverContactEmailPlaceholder": "Адрес email, по которому можно связаться с Вами", - "RecoverDescribeYourProblemPlaceholder": "Пожалуйста, опишите вашу проблему", - "RecoverTextBody": "Если Вы уже зарегистрированы и у Вас есть проблемы с доступом к этому порталу, или Вы хотите зарегистрироваться как новый пользователь портала, пожалуйста, обратитесь к администратору портала, используя форму, расположенную ниже.", - "RecoverTitle": "Доступ к порталу", "TurnOnDesktopVersion": "Включить версию для ПК" } diff --git a/packages/client/public/locales/sk/NavMenu.json b/packages/client/public/locales/sk/NavMenu.json index 5d079ee75b..a726713f7c 100644 --- a/packages/client/public/locales/sk/NavMenu.json +++ b/packages/client/public/locales/sk/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Obnoviť prístup", - "RecoverContactEmailPlaceholder": "Kontaktný email", - "RecoverDescribeYourProblemPlaceholder": "Opíšte svoj problém", - "RecoverTextBody": "Ak sa nemôžete prihlásiť so svojím existujúcim kontom, alebo sa chcete zaregistrovať ako nový užívateľ, kontaktujte admina portálu.", - "RecoverTitle": "Obnova prístupu", "TurnOnDesktopVersion": "Zapnúť počítačovú verziu" } diff --git a/packages/client/public/locales/sl/NavMenu.json b/packages/client/public/locales/sl/NavMenu.json index 01aefcb76f..6c32ed1ab1 100644 --- a/packages/client/public/locales/sl/NavMenu.json +++ b/packages/client/public/locales/sl/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Obnova dostopa", - "RecoverContactEmailPlaceholder": "Kontaktni email", - "RecoverDescribeYourProblemPlaceholder": "Opišite vaš problem", - "RecoverTextBody": "Če se ne morete prijaviti z obstoječim računom ali se želite registrirati kot nov uporabnik, se obrnite na skrbnika portala. ", - "RecoverTitle": "Dostop za obnovo", "TurnOnDesktopVersion": "Vključi namizno verzijo" } diff --git a/packages/client/public/locales/tr/NavMenu.json b/packages/client/public/locales/tr/NavMenu.json index b26e23e5d3..6aaedb0ff1 100644 --- a/packages/client/public/locales/tr/NavMenu.json +++ b/packages/client/public/locales/tr/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Erişimi kurtar", - "RecoverContactEmailPlaceholder": "İletişim e-postası", - "RecoverDescribeYourProblemPlaceholder": "Sorununuzu tarif edin", - "RecoverTextBody": "Eğer hesabınıza giriş yapamıyorsanız veya yeni kullanıcı olarak kayıt olmak istiyorsanız, portal yöneticisi ile iletişime geçin. ", - "RecoverTitle": "Erişim kurtarma", "TurnOnDesktopVersion": "Masaüstü sürümünü aç" } diff --git a/packages/client/public/locales/uk/NavMenu.json b/packages/client/public/locales/uk/NavMenu.json index ddb18ec12e..e8e379490d 100644 --- a/packages/client/public/locales/uk/NavMenu.json +++ b/packages/client/public/locales/uk/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Відновити доступ", - "RecoverContactEmailPlaceholder": "Контактна електронна пошта", - "RecoverDescribeYourProblemPlaceholder": "Опишіть свою проблему", - "RecoverTextBody": "Якщо ви не можете увійти зі своїм існуючим обліковим записом або хочете зареєструватися як новий користувач, зверніться до адміністратора порталу.", - "RecoverTitle": "Відновлення доступу", "TurnOnDesktopVersion": "Увімкнути настільну версію" } diff --git a/packages/client/public/locales/vi/NavMenu.json b/packages/client/public/locales/vi/NavMenu.json index ef8f7baf53..39b332ec6a 100644 --- a/packages/client/public/locales/vi/NavMenu.json +++ b/packages/client/public/locales/vi/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "Khôi phục quyền truy cập", - "RecoverContactEmailPlaceholder": "Email liên hệ", - "RecoverDescribeYourProblemPlaceholder": "Mô tả vấn đề của bạn", - "RecoverTextBody": "Nếu bạn không thể đăng nhập bằng tài khoản hiện có của mình hoặc muốn đăng ký làm người dùng mới, hãy liên hệ với quản trị viên cổng thông tin.", - "RecoverTitle": "Khôi phục truy cập", "TurnOnDesktopVersion": "Bật phiên bản dành cho máy tính" } diff --git a/packages/client/public/locales/zh-CN/NavMenu.json b/packages/client/public/locales/zh-CN/NavMenu.json index 597ffab419..68c3e2fff7 100644 --- a/packages/client/public/locales/zh-CN/NavMenu.json +++ b/packages/client/public/locales/zh-CN/NavMenu.json @@ -1,8 +1,3 @@ { - "RecoverAccess": "恢复访问", - "RecoverContactEmailPlaceholder": "联系邮箱", - "RecoverDescribeYourProblemPlaceholder": "描述您的问题", - "RecoverTextBody": "如果您无法使用现有账户登录或希望注册为新用户,请联系门户管理员。", - "RecoverTitle": "访问恢复", "TurnOnDesktopVersion": "打开桌面版" } diff --git a/packages/client/src/DocspaceLogo/index.js b/packages/client/src/DocspaceLogo/index.js new file mode 100644 index 0000000000..e2010202c2 --- /dev/null +++ b/packages/client/src/DocspaceLogo/index.js @@ -0,0 +1,32 @@ +import React from "react"; +import styled from "styled-components"; +import { ReactSVG } from "react-svg"; +import { hugeMobile } from "@docspace/components/utils/device"; +import { isMobileOnly } from "react-device-detect"; + +const StyledWrapper = styled.div` + .logo-wrapper { + width: 100%; + height: 46px; + + @media ${hugeMobile} { + display: none; + } + } +`; + +const DocspaceLogo = (props) => { + const { className } = props; + if (isMobileOnly) return <>; + + return ( + + + + ); +}; + +export default DocspaceLogo; diff --git a/packages/client/src/Shell.jsx b/packages/client/src/Shell.jsx index 0fa74f456a..39785411fb 100644 --- a/packages/client/src/Shell.jsx +++ b/packages/client/src/Shell.jsx @@ -417,7 +417,6 @@ const Shell = ({ items = [], page = "home", ...rest }) => { const pathname = window.location.pathname.toLowerCase(); const isEditor = pathname.indexOf("doceditor") !== -1; - const isLogin = pathname.indexOf("login") !== -1; const loginRoutes = []; @@ -463,7 +462,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => { - {isEditor || isLogin || !isMobileOnly ? <> : } + {isEditor || !isMobileOnly ? <> : } diff --git a/packages/client/src/components/Main/index.js b/packages/client/src/components/Main/index.js index 34eaa8598c..99943eff7f 100644 --- a/packages/client/src/components/Main/index.js +++ b/packages/client/src/components/Main/index.js @@ -1,12 +1,6 @@ import React from "react"; import styled, { css } from "styled-components"; -import { - isIOS, - isFirefox, - isSafari, - isMobile, - isMobileOnly, -} from "react-device-detect"; +import { isIOS, isFirefox, isMobileOnly } from "react-device-detect"; const StyledMain = styled.main` height: ${isIOS && !isFirefox ? "calc(var(--vh, 1vh) * 100)" : "100vh"}; @@ -41,6 +35,7 @@ const Main = React.memo((props) => { // }, []); //console.log("Main render"); + return ; }); diff --git a/packages/client/src/components/NavMenu/sub-components/header-unauth.js b/packages/client/src/components/NavMenu/sub-components/header-unauth.js index 6531d18644..7e35079fb0 100644 --- a/packages/client/src/components/NavMenu/sub-components/header-unauth.js +++ b/packages/client/src/components/NavMenu/sub-components/header-unauth.js @@ -2,7 +2,6 @@ import React from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; import Box from "@docspace/components/box"; -import RecoverAccess from "./recover-access-container"; import { useTranslation } from "react-i18next"; import { inject, observer } from "mobx-react"; import { combineUrl } from "@docspace/common/utils"; @@ -24,7 +23,10 @@ const Header = styled.header` width: 475px; } @media (max-width: 375px) { - padding: 0 16px; + display: flex; + align-items: center; + justify-content: center; + //padding: 0 16px; } } @@ -40,10 +42,9 @@ const Header = styled.header` } .header-logo-icon { - width: 146px; - height: 24px; - position: relative; - padding: 3px 20px 0 6px; + width: 100%; + height: 100%; + padding: 12px 0; cursor: pointer; } `; @@ -69,18 +70,11 @@ const HeaderUnAuth = ({ {!isAuthenticated && isLoaded ? (
- @@ -88,8 +82,6 @@ const HeaderUnAuth = ({ ) : ( <> )} - -
{enableAdmMess && !wizardToken && }
); diff --git a/packages/client/src/components/NavMenu/sub-components/recover-access-container.js b/packages/client/src/components/NavMenu/sub-components/recover-access-container.js deleted file mode 100644 index 961e16111b..0000000000 --- a/packages/client/src/components/NavMenu/sub-components/recover-access-container.js +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useState } from "react"; -import styled from "styled-components"; -import PropTypes from "prop-types"; - -import Box from "@docspace/components/box"; -import Text from "@docspace/components/text"; -import toastr from "@docspace/components/toast/toastr"; -import UnionIcon from "../svg/union.react.svg"; -import RecoverAccessModalDialog from "./recover-access-modal-dialog"; -import { sendRecoverRequest } from "@docspace/common/api/settings/index"; -import commonIconsStyles from "@docspace/components/utils/common-icons-style"; -import { Base } from "@docspace/components/themes"; - -const StyledUnionIcon = styled(UnionIcon)` - ${commonIconsStyles} -`; - -const RecoverContainer = styled(Box)` - cursor: pointer; - background-color: ${(props) => props.theme.header.recoveryColor}; - - .recover-icon { - @media (max-width: 450px) { - padding: 12px; - } - } - .recover-text { - @media (max-width: 450px) { - display: none; - } - } -`; - -RecoverContainer.defaultProps = { theme: Base }; - -const RecoverAccess = ({ t }) => { - const [visible, setVisible] = useState(false); - const [loading, setLoading] = useState(false); - - const [email, setEmail] = useState(""); - const [emailErr, setEmailErr] = useState(false); - - const [description, setDescription] = useState(""); - const [descErr, setDescErr] = useState(false); - - const onRecoverClick = () => { - setVisible(true); - }; - const onRecoverModalClose = () => { - setVisible(false); - setEmail(""); - setEmailErr(false); - setDescription(""); - setDescErr(false); - }; - const onChangeEmail = (e) => { - setEmail(e.currentTarget.value); - setEmailErr(false); - }; - const onChangeDescription = (e) => { - setDescription(e.currentTarget.value); - setDescErr(false); - }; - const onSendRecoverRequest = () => { - if (!email.trim()) { - setEmailErr(true); - } - if (!description.trim()) { - setDescErr(true); - } else { - setLoading(true); - sendRecoverRequest(email, description) - .then((res) => { - setLoading(false); - toastr.success(res); - }) - .catch((error) => { - setLoading(false); - toastr.error(error); - }) - .finally(onRecoverModalClose); - } - }; - - return ( - <> - - - - - - - - {t("RecoverAccess")} - - - - - {visible && ( - - )} - - ); -}; - -RecoverAccess.propTypes = { - t: PropTypes.func.isRequired, -}; - -export default RecoverAccess; diff --git a/packages/client/src/pages/Confirm/ConfirmWrapper.js b/packages/client/src/pages/Confirm/ConfirmWrapper.js new file mode 100644 index 0000000000..ed58592877 --- /dev/null +++ b/packages/client/src/pages/Confirm/ConfirmWrapper.js @@ -0,0 +1,26 @@ +import React from "react"; +import styled, { css } from "styled-components"; +import { isIOS, isFirefox, isMobileOnly } from "react-device-detect"; + +const StyledWrapper = styled.div` + height: ${isIOS && !isFirefox ? "calc(var(--vh, 1vh) * 100)" : "100vh"}; + width: 100vw; + z-index: 0; + display: flex; + flex-direction: row; + box-sizing: border-box; + + ${isMobileOnly && + css` + height: auto; + min-height: 100%; + width: 100%; + `} +`; + +const ConfirmWrapper = (props) => { + const { children, className } = props; + return {children}; +}; + +export default ConfirmWrapper; diff --git a/packages/client/src/pages/Confirm/index.js b/packages/client/src/pages/Confirm/index.js index e67eefeb0c..91f9549439 100644 --- a/packages/client/src/pages/Confirm/index.js +++ b/packages/client/src/pages/Confirm/index.js @@ -1,6 +1,7 @@ import React, { lazy } from "react"; import { Switch } from "react-router-dom"; -import ConfirmRoute from "SRC_DIR/helpers/confirmRoute"; +import ConfirmRoute from "../../helpers/confirmRoute"; +import ConfirmWrapper from "./ConfirmWrapper"; const ActivateUserForm = lazy(() => import("./sub-components/activateUser")); const CreateUserForm = lazy(() => import("./sub-components/createUser")); @@ -19,56 +20,58 @@ const Confirm = ({ match }) => { //console.log("Confirm render"); const path = match.path; return ( - - - - - - - - - - - + + + + + + + + + + + + - {/* */} - + {/* */} + + ); }; diff --git a/packages/client/src/pages/Confirm/sub-components/StyledConfirm.js b/packages/client/src/pages/Confirm/sub-components/StyledConfirm.js index 833747f18a..c2ab921c04 100644 --- a/packages/client/src/pages/Confirm/sub-components/StyledConfirm.js +++ b/packages/client/src/pages/Confirm/sub-components/StyledConfirm.js @@ -13,28 +13,41 @@ export const StyledPage = styled.div` } @media ${mobile} { - margin-top: 72px; - } -`; - -export const StyledHeader = styled.div` - text-align: left; - - .title { - margin-bottom: 24px; + margin-top: 32px; } .subtitle { margin-bottom: 32px; } + + .password-form { + width: 100%; + margin-bottom: 8px; + } +`; + +export const StyledHeader = styled.div` + .title { + margin-bottom: 32px; + text-align: center; + } + + .subtitle { + margin-bottom: 32px; + } + + .docspace-logo { + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 64px; + } `; export const StyledBody = styled.div` - width: 320px; - - @media ${tablet} { - justify-content: center; - } + display: flex; + flex-direction: column; + align-items: center; @media ${mobile} { width: 100%; @@ -48,37 +61,27 @@ export const StyledBody = styled.div` width: 100%; } - .confirm-button { - width: 100%; - margin-top: 8px; - } - .password-change-form { margin-top: 32px; margin-bottom: 16px; } - .confirm-subtitle { - margin-bottom: 8px; - } - - .info-delete { + .phone-input { margin-bottom: 24px; } - .phone-input { - margin-top: 32px; - margin-bottom: 16px; + .delete-profile-confirm { + margin-bottom: 8px; + } + + .phone-title { + margin-bottom: 8px; } `; export const ButtonsWrapper = styled.div` display: flex; - flex: 1fr 1fr; flex-direction: row; gap: 16px; - - .button { - width: 100%; - } + width: 100%; `; diff --git a/packages/client/src/pages/Confirm/sub-components/changeOwner.js b/packages/client/src/pages/Confirm/sub-components/changeOwner.js index f13c0854d1..4cfcfae77b 100644 --- a/packages/client/src/pages/Confirm/sub-components/changeOwner.js +++ b/packages/client/src/pages/Confirm/sub-components/changeOwner.js @@ -12,6 +12,8 @@ import { ButtonsWrapper, } from "./StyledConfirm"; import withLoader from "../withLoader"; +import FormWrapper from "@docspace/components/form-wrapper"; +import DocspaceLogo from "../../../DocspaceLogo"; const ChangeOwnerForm = (props) => { const { t, greetingTitle } = props; @@ -20,34 +22,36 @@ const ChangeOwnerForm = (props) => { + {greetingTitle} + + {t("ConfirmOwnerPortalTitle", { newOwner: "NEW OWNER" })} - - - -
- - {ssoExists() && {ssoButton()}} + + + {ssoExists() && {ssoButton()}} - {oauthDataExists() && ( - <> - {providerButtons()} - {providers && providers.length > 2 && ( - - {t("Common:ShowMore")} - - )} - - )} + {oauthDataExists() && ( + <> + {providerButtons()} + {providers && providers.length > 2 && ( + + {t("Common:ShowMore")} + + )} + + )} - {(oauthDataExists() || ssoExists()) && ( -
- - {t("Or")} - -
- )} + {(oauthDataExists() || ssoExists()) && ( +
+ + {t("Or")} + +
+ )} -
-
- - +
+ - + errorMessage={ + emailErrorText + ? t(`Common:${emailErrorText}`) + : t("Common:RequiredField") + } + > + + - - - + errorMessage={errorText ? errorText : t("Common:RequiredField")} + > + + - - - + errorMessage={errorText ? errorText : t("Common:RequiredField")} + > + + - - + + + +
+
-