Merge branch 'develop' of github.com:ONLYOFFICE/AppServer into develop

This commit is contained in:
mushka 2022-08-25 12:25:57 +03:00
commit eb503c292f
187 changed files with 4824 additions and 2242 deletions

14
build/Jenkinsfile vendored
View File

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

View File

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

View File

@ -1,5 +1,6 @@
@echo "MIGRATIONS"
@echo off
PUSHD %~dp0..\common\Tools\Migration.Creator
dotnet run --project Migration.Creator.csproj
PUSHD %~dp0..\common\Tools\ASC.Migration.Creator
dotnet run --project ASC.Migration.Creator.csproj
pause

View File

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

View File

@ -18,6 +18,7 @@
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" Version="6.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="6.0.5" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.7" />
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.1.0" />

View File

@ -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<AuthenticationSchemeOptions>
{
private readonly UserManager _userManager;
private readonly SecurityContext _securityContext;
private readonly PasswordHasher _passwordHasher;
public BasicAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock
) : base(options, logger, encoder, clock)
{
}
public BasicAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> 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<AuthenticateResult> 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)));
}
}

View File

@ -31,9 +31,9 @@ namespace ASC.Api.Core.Auth;
[Scope]
public class CookieAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly AuthorizationHelper _authorizationHelper;
private readonly SecurityContext _securityContext;
private readonly CookiesManager _cookiesManager;
private readonly IHttpContextAccessor _httpContextAccessor;
public CookieAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
@ -42,31 +42,59 @@ public class CookieAuthHandler : AuthenticationHandler<AuthenticationSchemeOptio
ISystemClock clock)
: base(options, logger, encoder, clock) { }
public CookieAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock,
AuthorizationHelper authorizationHelper,
public CookieAuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> 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<AuthenticateResult> 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)));
}
}

View File

@ -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<TenantDbContext>();
services.AddBaseDbContextPool<UserDbContext>();
services.AddBaseDbContextPool<TelegramDbContext>();
services.AddBaseDbContextPool<FirebaseDbContext>();
services.AddBaseDbContextPool<CustomDbContext>();
services.AddBaseDbContextPool<WebstudioDbContext>();
services.AddBaseDbContextPool<InstanceRegistrationContext>();
@ -102,13 +108,13 @@ public abstract class BaseStartup
services.AddSingleton(jsonOptions);
DIHelper.AddControllers();
DIHelper.TryAdd<DisposeMiddleware>();
DIHelper.TryAdd<CultureMiddleware>();
DIHelper.TryAdd<IpSecurityFilter>();
DIHelper.TryAdd<PaymentFilter>();
DIHelper.TryAdd<ProductSecurityFilter>();
DIHelper.TryAdd<TenantStatusFilter>();
DIHelper.TryAdd<ConfirmAuthHandler>();
DIHelper.TryAdd<BasicAuthHandler>();
DIHelper.TryAdd<CookieAuthHandler>();
DIHelper.TryAdd<WebhooksGlobalFilterAttribute>();
@ -147,16 +153,72 @@ public abstract class BaseStartup
config.OutputFormatters.Add(new XmlOutputFormatter());
});
var authBuilder = services.AddAuthentication("cookie")
.AddScheme<AuthenticationSchemeOptions, CookieAuthHandler>("cookie", a => { });
if (ConfirmAddScheme)
var authBuilder = services.AddAuthentication(options =>
{
authBuilder.AddScheme<AuthenticationSchemeOptions, ConfirmAuthHandler>("confirm", a => { });
}
options.DefaultScheme = "MultiAuthSchemes";
options.DefaultChallengeScheme = "MultiAuthSchemes";
}).AddScheme<AuthenticationSchemeOptions, CookieAuthHandler>(CookieAuthenticationDefaults.AuthenticationScheme, a => { })
.AddScheme<AuthenticationSchemeOptions, BasicAuthHandler>("Basic", a => { })
.AddScheme<AuthenticationSchemeOptions, ConfirmAuthHandler>("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<ASC.Core.SecurityContext>();
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<WarmupServicesStartupTask>()
@ -189,8 +251,6 @@ public abstract class BaseStartup
app.UseCultureMiddleware();
app.UseDisposeMiddleware();
app.UseEndpoints(endpoints =>
{
endpoints.MapCustom();

View File

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

View File

@ -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<object> list)
{
return list.Count;
}
if (Response is IEnumerable<object> 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<object> list)
{
Count = list.Count;
}
else if (response is IEnumerable<object> collection)
{
Count = collection.Count();
}
else if (response == null)
{
Count = 0;
}
else
{
Count = 1;
}
}
}
}

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using CallContext = ASC.Common.Notify.Engine.CallContext;
namespace ASC.Api.Core.Middleware;
[Scope]

View File

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

View File

@ -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<WebhooksGlobalFilterAttribute> _logger;
private static readonly List<string> _methodList = new List<string> { "POST", "UPDATE", "DELETE" };
public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher)
public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher, ILogger<WebhooksGlobalFilterAttribute> 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);
}
}
}

View File

@ -77,7 +77,6 @@ public class DistributedTask
Publication(this);
}
[Obsolete("GetProperty<T> is deprecated, please use indexer this[propName] instead.")]
public T GetProperty<T>(string propName)
{
if (!_props.TryGetValue(propName, out var propValue))
@ -88,8 +87,7 @@ public class DistributedTask
return JsonSerializer.Deserialize<T>(propValue);
}
[Obsolete("SetProperty is deprecated, please use indexer this[propName] = propValue instead.")]
public void SetProperty(string propName, object propValue)
public void SetProperty<T>(string propName, T propValue)
{
_props[propName] = JsonSerializer.Serialize(propValue);
}

View File

@ -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<string, IDisposable> Items
{
get
{
var table = (Dictionary<string, IDisposable>)_context.Items[Key];
if (table == null)
{
table = new Dictionary<string, IDisposable>(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;
}
}
}

View File

@ -50,6 +50,7 @@
<ItemGroup>
<PackageReference Include="AWSSDK.Core" Version="3.7.10.11" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.0.150" />
<PackageReference Include="FirebaseAdmin" Version="2.3.0" />
<PackageReference Include="Grpc.Tools" Version="2.47.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

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

View File

@ -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<NotifyTransferRequest>();
@ -199,6 +205,7 @@ public static class WorkContextExtension
dIHelper.TryAdd<TelegramHelper>();
dIHelper.TryAdd<TelegramSenderSinkMessageCreator>();
dIHelper.TryAdd<JabberSenderSinkMessageCreator>();
dIHelper.TryAdd<PushSenderSinkMessageCreator>();
dIHelper.TryAdd<EmailSenderSinkMessageCreator>();
}
}

View File

@ -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<DisposeMiddleware>();
}
}
// (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<FireBaseUser> Users { get; set; }
public FirebaseDbContext(DbContextOptions<FirebaseDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
ModelBuilderWrapper
.From(modelBuilder, Database)
.AddFireBaseUsers();
}
}

View File

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

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ASC.Core.Common.EF.Model;
public class ModelBuilderWrapper
@ -75,6 +77,11 @@ public class ModelBuilderWrapper
return this;
}
public EntityTypeBuilder<T> Entity<T>() where T : class
{
return ModelBuilder.Entity<T>();
}
public void AddDbFunction()
{
ModelBuilder

View File

@ -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;
global using JsonIgnoreAttribute = System.Text.Json.Serialization.JsonIgnoreAttribute;
global using FirebaseAdminMessaging = FirebaseAdmin.Messaging;
global using FirebaseApp = FirebaseAdmin.FirebaseApp;
global using AppOptions = FirebaseAdmin.AppOptions;

View File

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

View File

@ -65,6 +65,12 @@ public class NotifyMessage : IMapFrom<NotifyQueue>
[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<NotifyQueue, NotifyMessage>()

View File

@ -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<FirebaseDbContext> _dbContextFactory;
public FirebaseDao(IDbContextFactory<FirebaseDbContext> 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<FireBaseUser> 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;
}
}

View File

@ -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<WebhookRequest> Queue;
private readonly ICacheNotify<WebhookRequest> _webhookNotify;
[JsonProperty("portal")]
public string Portal { get; set; }
public BuildQueueService(ICacheNotify<WebhookRequest> webhookNotify)
{
_webhookNotify = webhookNotify;
Queue = new ConcurrentQueue<WebhookRequest>();
}
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;
}
}
[JsonProperty("originalUrl")]
public string OriginalUrl { get; set; }
}

View File

@ -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<OFormSettings>("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; }
}

View File

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

View File

@ -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<FirebaseHelper> _logger;
private readonly IConfiguration _configuration;
private readonly FirebaseDao _firebaseDao;
public FirebaseHelper(
AuthContext authContext,
UserManager userManager,
TenantManager tenantManager,
IConfiguration configuration,
ILogger<FirebaseHelper> 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<FireBaseUser>();
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<string, string>{
{ "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);
}
}

View File

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

View File

@ -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<PushSenderSink> _logger;
private bool _configured = true;
private static readonly string _senderName = Constants.NotifyPushSenderSysName;
private readonly INotifySender _sender;
public PushSenderSink(IServiceProvider serviceProvider, ILogger<PushSenderSink> 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<TenantManager>();
var notification = new PushNotification
var m = scope.ServiceProvider.GetRequiredService<PushSenderSinkMessageCreator>().CreateNotifyMessage(message, _senderName);
if (string.IsNullOrEmpty(m.Reciever))
{
Module = GetTagValue<PushModule>(message, PushConstants.PushModuleTagName),
Action = GetTagValue<PushAction>(message, PushConstants.PushActionTagName),
Item = GetTagValue<PushItem>(message, PushConstants.PushItemTagName),
ParentItem = GetTagValue<PushItem>(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<string>());
}
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;
}
}

View File

@ -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<string, string> 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>();
FirebaseHelper.SendMessage(m);
}
catch (Exception e)
{
_logger.ErrorUnexpected(e);
}
return NoticeSendResult.OK;
}
}
public static class FirebaseSenderExtension
{
public static void Register(DIHelper services)
{
services.TryAdd<FirebaseHelper>();
}
}

View File

@ -30,117 +30,186 @@ namespace ASC.Webhooks.Core;
public class DbWorker
{
private readonly IDbContextFactory<WebhooksDbContext> _dbContextFactory;
private readonly TenantManager _tenantManager;
public DbWorker(IDbContextFactory<WebhooksDbContext> dbContextFactory, TenantManager tenantManager)
private readonly TenantManager _tenantManager;
private readonly AuthContext _authContext;
private int Tenant
{
get
{
return _tenantManager.GetCurrentTenant().Id;
}
}
public DbWorker(IDbContextFactory<WebhooksDbContext> 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<WebhooksConfig> 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<WebhooksLog> GetTenantWebhooks()
public async IAsyncEnumerable<WebhooksConfig> 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<WebhooksConfig> GetWebhookConfigs()
{
var webhooksDbContext = _dbContextFactory.CreateDbContext();
return webhooksDbContext.WebhooksConfigs
.Where(t => t.TenantId == Tenant)
.AsAsyncEnumerable();
}
public async Task<WebhooksConfig> 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<WebhooksConfig> 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<WebhooksConfig> 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<WebhooksLog> 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<WebhooksLog> ReadJournal(int id)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
return await webhooksDbContext.WebhooksLogs
.AsNoTracking()
.Where(it => it.Id == id)
.FirstOrDefaultAsync();
}
public async Task<WebhooksLog> 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<WebhooksLog> 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;
}
}

View File

@ -28,8 +28,8 @@ namespace ASC.Webhooks.Core.EF.Context;
public class WebhooksDbContext : DbContext
{
public virtual DbSet<WebhooksConfig> WebhooksConfigs { get; set; }
public virtual DbSet<WebhooksLog> WebhooksLogs { get; set; }
public DbSet<WebhooksConfig> WebhooksConfigs { get; set; }
public DbSet<WebhooksLog> WebhooksLogs { get; set; }
public WebhooksDbContext(DbContextOptions<WebhooksDbContext> options) : base(options) { }

View File

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

View File

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

View File

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

View File

@ -30,51 +30,56 @@ namespace ASC.Webhooks.Core;
public class WebhookPublisher : IWebhookPublisher
{
private readonly DbWorker _dbWorker;
private readonly TenantManager _tenantManager;
private readonly ICacheNotify<WebhookRequest> _webhookNotify;
public WebhookPublisher(
DbWorker dbWorker,
TenantManager tenantManager,
ICacheNotify<WebhookRequest> 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<WebhooksLog> 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;
}
}

View File

@ -58,6 +58,7 @@ builder.WebHost.ConfigureServices((hostContext, services) =>
services.AddBaseDbContext<FilesDbContext>();
services.AddBaseDbContext<NotifyDbContext>();
services.AddBaseDbContext<UrlShortenerFakeDbContext>();
services.AddBaseDbContext<FirebaseDbContext>();
});
var app = builder.Build();

View File

@ -45,6 +45,7 @@ builder.WebHost.ConfigureServices((hostContext, services) =>
services.AddBaseDbContext<TenantDbContext>();
services.AddBaseDbContext<UserDbContext>();
services.AddBaseDbContext<TelegramDbContext>();
services.AddBaseDbContext<FirebaseDbContext>();
services.AddBaseDbContext<CustomDbContext>();
services.AddBaseDbContext<WebstudioDbContext>();
services.AddBaseDbContext<InstanceRegistrationContext>();

View File

@ -6,6 +6,10 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="6.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ASC.Api.Core\ASC.Api.Core.csproj" />
</ItemGroup>

View File

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

View File

@ -41,11 +41,20 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) =>
diHelper.TryAdd<DbWorker>();
services.AddHostedService<BuildQueueService>();
diHelper.TryAdd<BuildQueueService>();
services.AddHostedService<WorkerService>();
diHelper.TryAdd<WorkerService>();
diHelper.TryAdd<WorkerService>();
services.AddHttpClient("webhook")
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddPolicyHandler((s, request) =>
{
var settings = s.GetRequiredService<Settings>();
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();

View File

@ -29,26 +29,36 @@ namespace ASC.Webhooks.Service.Services;
[Singletone]
public class WorkerService : BackgroundService
{
private readonly ILogger<WorkerService> _logger;
private readonly ConcurrentQueue<WebhookRequest> _queue;
private readonly ILogger<WorkerService> _logger;
private readonly int? _threadCount = 10;
private readonly WebhookSender _webhookSender;
private readonly TimeSpan _waitingPeriod;
private readonly TimeSpan _waitingPeriod;
private readonly ConcurrentQueue<WebhookRequest> _queue;
private readonly ICacheNotify<WebhookRequest> _webhookNotify;
public WorkerService(WebhookSender webhookSender,
public WorkerService(
ICacheNotify<WebhookRequest> webhookNotify,
WebhookSender webhookSender,
ILogger<WorkerService> logger,
BuildQueueService buildQueueService,
Settings settings)
{
_logger = logger;
{
_webhookNotify = webhookNotify;
_queue = new ConcurrentQueue<WebhookRequest>();
_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<Task>();
var tasks = new List<Task>(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();
}
}

View File

@ -37,7 +37,7 @@ public class Settings
{
var cfg = configuration.GetSetting<Settings>("webhooks");
RepeatCount = cfg.RepeatCount ?? 5;
ThreadCount = cfg.ThreadCount ?? 1;
ThreadCount = cfg.ThreadCount ?? 10;
}
public int? RepeatCount { get; }
public int? ThreadCount { get; }

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using 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<DbWorker>();
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);
}
}

View File

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

View File

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

View File

@ -0,0 +1,69 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Application")
.HasColumnType("varchar(20)")
.HasColumnName("application")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("FirebaseDeviceToken")
.HasColumnType("varchar(255)")
.HasColumnName("firebase_device_token")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<bool?>("IsSubscribed")
.HasColumnType("tinyint(1)")
.HasColumnName("is_subscribed");
b.Property<int>("TenantId")
.HasColumnType("int")
.HasColumnName("tenant_id");
b.Property<string>("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
}
}
}

View File

@ -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<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
user_id = table.Column<string>(type: "varchar(36)", nullable: false, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
tenant_id = table.Column<int>(type: "int", nullable: false),
firebase_device_token = table.Column<string>(type: "varchar(255)", nullable: true, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
application = table.Column<string>(type: "varchar(20)", nullable: true, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
is_subscribed = table.Column<bool>(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");
}
}
}

View File

@ -0,0 +1,67 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Application")
.HasColumnType("varchar(20)")
.HasColumnName("application")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("FirebaseDeviceToken")
.HasColumnType("varchar(255)")
.HasColumnName("firebase_device_token")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<bool?>("IsSubscribed")
.HasColumnType("tinyint(1)")
.HasColumnName("is_subscribed");
b.Property<int>("TenantId")
.HasColumnType("int")
.HasColumnName("tenant_id");
b.Property<string>("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
}
}
}

View File

@ -0,0 +1,163 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasColumnName("enabled")
.HasDefaultValueSql("'1'");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("name");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("secret_key")
.HasDefaultValueSql("''");
b.Property<uint>("TenantId")
.HasColumnType("int unsigned")
.HasColumnName("tenant_id");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime")
.HasColumnName("creation_time");
b.Property<DateTime?>("Delivery")
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
b.Property<string>("RequestPayload")
.IsRequired()
.HasColumnType("text")
.HasColumnName("request_payload")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("ResponseHeaders")
.HasColumnType("json")
.HasColumnName("response_headers");
b.Property<string>("ResponsePayload")
.HasColumnType("text")
.HasColumnName("response_payload")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("route");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");
b.Property<uint>("TenantId")
.HasColumnType("int unsigned")
.HasColumnName("tenant_id");
b.Property<string>("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
}
}
}

View File

@ -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<string>(
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<int>(
name: "status",
table: "webhooks_logs",
type: "int",
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(50)",
oldMaxLength: 50)
.OldAnnotation("MySql:CharSet", "utf8");
migrationBuilder.AddColumn<DateTime>(
name: "delivery",
table: "webhooks_logs",
type: "datetime",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "enabled",
table: "webhooks_config",
type: "tinyint(1)",
nullable: false,
defaultValueSql: "'1'");
migrationBuilder.AddColumn<string>(
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<string>(
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<string>(
name: "status",
table: "webhooks_logs",
type: "varchar(50)",
maxLength: 50,
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:CharSet", "utf8");
}
}
}

View File

@ -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<int>("ConfigId")
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("config_id");
.HasColumnName("id");
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("tinyint(1)")
.HasColumnName("enabled")
.HasDefaultValueSql("'1'");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("name");
b.Property<string>("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<string>("Event")
b.Property<DateTime?>("Delivery")
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("event");
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
@ -78,21 +97,28 @@ namespace ASC.Migrations.MySql.Migrations
b.Property<string>("RequestPayload")
.IsRequired()
.HasColumnType("json")
.HasColumnName("request_payload");
.HasColumnType("text")
.HasColumnName("request_payload")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("ResponseHeaders")
.HasColumnType("json")
.HasColumnName("response_headers");
b.Property<string>("ResponsePayload")
.HasColumnType("json")
.HasColumnName("response_payload");
.HasColumnType("text")
.HasColumnName("response_payload")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("route");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");
b.Property<uint>("TenantId")
@ -100,17 +126,35 @@ namespace ASC.Migrations.MySql.Migrations
.HasColumnName("tenant_id");
b.Property<string>("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
}
}

View File

@ -0,0 +1,68 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Application")
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("application");
b.Property<string>("FirebaseDeviceToken")
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("firebase_device_token");
b.Property<bool?>("IsSubscribed")
.HasColumnType("boolean")
.HasColumnName("is_subscribed");
b.Property<int>("TenantId")
.HasColumnType("integer")
.HasColumnName("tenant_id");
b.Property<Guid>("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
}
}
}

View File

@ -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<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
user_id = table.Column<Guid>(type: "uuid", maxLength: 36, nullable: false),
tenant_id = table.Column<int>(type: "integer", nullable: false),
firebase_device_token = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
application = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true),
is_subscribed = table.Column<bool>(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");
}
}
}

View File

@ -0,0 +1,66 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Application")
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("application");
b.Property<string>("FirebaseDeviceToken")
.HasMaxLength(255)
.HasColumnType("character varying(255)")
.HasColumnName("firebase_device_token");
b.Property<bool?>("IsSubscribed")
.HasColumnType("boolean")
.HasColumnName("is_subscribed");
b.Property<int>("TenantId")
.HasColumnType("integer")
.HasColumnName("tenant_id");
b.Property<Guid>("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
}
}
}

View File

@ -0,0 +1,158 @@
// <auto-generated />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasColumnName("enabled")
.HasDefaultValueSql("true");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("secret_key")
.HasDefaultValueSql("''");
b.Property<int>("TenantId")
.HasColumnType("int unsigned")
.HasColumnName("tenant_id");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime")
.HasColumnName("creation_time");
b.Property<DateTime?>("Delivery")
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
b.Property<string>("RequestPayload")
.IsRequired()
.HasColumnType("text")
.HasColumnName("request_payload");
b.Property<string>("ResponseHeaders")
.HasColumnType("json")
.HasColumnName("response_headers");
b.Property<string>("ResponsePayload")
.HasColumnType("text")
.HasColumnName("response_payload");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("route");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");
b.Property<int>("TenantId")
.HasColumnType("int unsigned")
.HasColumnName("tenant_id");
b.Property<string>("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
}
}
}

View File

@ -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<string>(
name: "uid",
table: "webhooks_logs",
type: "varchar",
maxLength: 50,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "varchar",
oldMaxLength: 50,
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "status",
table: "webhooks_logs",
type: "int",
nullable: false,
oldClrType: typeof(string),
oldType: "varchar",
oldMaxLength: 50);
migrationBuilder.AddColumn<DateTime>(
name: "delivery",
table: "webhooks_logs",
type: "datetime",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "enabled",
table: "webhooks_config",
type: "boolean",
nullable: false,
defaultValueSql: "true");
migrationBuilder.AddColumn<string>(
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<string>(
name: "uid",
table: "webhooks_logs",
type: "varchar",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "varchar",
oldMaxLength: 50);
migrationBuilder.AlterColumn<string>(
name: "status",
table: "webhooks_logs",
type: "varchar",
maxLength: 50,
nullable: false,
oldClrType: typeof(int),
oldType: "int");
}
}
}

View File

@ -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<int>("ConfigId")
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("config_id")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasColumnName("enabled")
.HasDefaultValueSql("true");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b.Property<string>("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<string>("Event")
b.Property<DateTime?>("Delivery")
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("event");
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
@ -80,7 +99,7 @@ namespace ASC.Migrations.PostgreSql.Migrations
b.Property<string>("RequestPayload")
.IsRequired()
.HasColumnType("json")
.HasColumnType("text")
.HasColumnName("request_payload");
b.Property<string>("ResponseHeaders")
@ -88,13 +107,16 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnName("response_headers");
b.Property<string>("ResponsePayload")
.HasColumnType("json")
.HasColumnType("text")
.HasColumnName("response_payload");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(50)
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("route");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");
b.Property<int>("TenantId")
@ -102,6 +124,7 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnName("tenant_id");
b.Property<string>("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
}
}

View File

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

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "Поднови достъп",
"RecoverContactEmailPlaceholder": "Имейл за контакт",
"RecoverDescribeYourProblemPlaceholder": "Опишете проблема си",
"RecoverTextBody": "Ако не можете да се впишете със съществуващия си профил или искате да се регистрирате като нов потребител, свържете се с администратора на портала. ",
"RecoverTitle": "Възстановяване на достъп",
"TurnOnDesktopVersion": "Превключете на настолна версия"
}

View File

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

View File

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

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "Ανάκτηση πρόσβασης",
"RecoverContactEmailPlaceholder": "Email επικοινωνίας",
"RecoverDescribeYourProblemPlaceholder": "Περιγράψτε το πρόβλημά σας",
"RecoverTextBody": "Εάν δεν μπορείτε να συνδεθείτε με τον υπάρχοντα λογαριασμό σας ή θέλετε να εγγραφείτε ως νέος χρήστης, επικοινωνήστε με τον διαχειριστή της πύλης.",
"RecoverTitle": "Ανάκτηση πρόσβασης",
"TurnOnDesktopVersion": "Ενεργοποιήστε την έκδοση για υπολογιστές"
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "Վերականգնել մատչումը",
"RecoverContactEmailPlaceholder": "Կոնտակտային էլ. փոստ",
"RecoverDescribeYourProblemPlaceholder": "Նկարագրեք Ձեր խնդիրը",
"RecoverTextBody": "Եթե ​​Դուք չեք կարող մուտք գործել Ձեր գոյություն ունեցող հաշիվ կամ ցանկանում եք գրանցվել որպես նոր օգտվող, կապվեք կայքէջի ադմինիստրատորի հետ:",
"RecoverTitle": "Մատչման վերականգնում",
"TurnOnDesktopVersion": "Միացնել աշխատասեղանի տարբերակը"
}

View File

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

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "アクセス回復",
"RecoverContactEmailPlaceholder": "連絡先メール",
"RecoverDescribeYourProblemPlaceholder": "ご質問を説明ください",
"RecoverTextBody": "既存のアカウントでログインできない場合や、新規ユーザーとして登録したい場合は、ポータル管理者にお問い合わせください。 ",
"RecoverTitle": "アクセス回復機能",
"TurnOnDesktopVersion": "デスクトップ版を起動する"
}

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "액세스 복구",
"RecoverContactEmailPlaceholder": "연락처 이메일",
"RecoverDescribeYourProblemPlaceholder": "겪고 계신 문제를 설명해주세요",
"RecoverTextBody": "기존 계정으로 로그인하실 수 없거나 새로운 사용자로 등록하시길 원하시는 경우, 포털 관리자에게 문의하세요. ",
"RecoverTitle": "액세스 복원",
"TurnOnDesktopVersion": "데스크탑 버전 켜기"
}

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "ກູ້ຄືນການເຂົ້າເຖິງ",
"RecoverContactEmailPlaceholder": "ອີເມວຕິດຕໍ່",
"RecoverDescribeYourProblemPlaceholder": "ອະທິບາຍບັນຫາຂອງທ່ານ",
"RecoverTextBody": "ຖ້າທ່ານບໍ່ສາມາດເຂົ້າສູ່ລະບົບໄດ້ຫຼືຕ້ອງການລົງທະບຽນໃຫມ່, ໃຫ້ທ່ານຕິດຕໍ່ຜູ້ດູແລລະບົບ portal ",
"RecoverTitle": "ການເຂົ້າເຖິງການກູ້ຄືນ",
"TurnOnDesktopVersion": "ເປີດທາງ desktop"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "Доступ к порталу",
"RecoverContactEmailPlaceholder": "Адрес email, по которому можно связаться с Вами",
"RecoverDescribeYourProblemPlaceholder": "Пожалуйста, опишите вашу проблему",
"RecoverTextBody": "Если Вы уже зарегистрированы и у Вас есть проблемы с доступом к этому порталу, или Вы хотите зарегистрироваться как новый пользователь портала, пожалуйста, обратитесь к администратору портала, используя форму, расположенную ниже.",
"RecoverTitle": "Доступ к порталу",
"TurnOnDesktopVersion": "Включить версию для ПК"
}

View File

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

View File

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

View File

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

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "Відновити доступ",
"RecoverContactEmailPlaceholder": "Контактна електронна пошта",
"RecoverDescribeYourProblemPlaceholder": "Опишіть свою проблему",
"RecoverTextBody": "Якщо ви не можете увійти зі своїм існуючим обліковим записом або хочете зареєструватися як новий користувач, зверніться до адміністратора порталу.",
"RecoverTitle": "Відновлення доступу",
"TurnOnDesktopVersion": "Увімкнути настільну версію"
}

View File

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

View File

@ -1,8 +1,3 @@
{
"RecoverAccess": "恢复访问",
"RecoverContactEmailPlaceholder": "联系邮箱",
"RecoverDescribeYourProblemPlaceholder": "描述您的问题",
"RecoverTextBody": "如果您无法使用现有账户登录或希望注册为新用户,请联系门户管理员。",
"RecoverTitle": "访问恢复",
"TurnOnDesktopVersion": "打开桌面版"
}

View File

@ -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 (
<StyledWrapper>
<ReactSVG
src="/static/images/docspace.big.react.svg"
className={`logo-wrapper ${className}`}
/>
</StyledWrapper>
);
};
export default DocspaceLogo;

View File

@ -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 }) => {
<Router history={history}>
<Toast />
<ReactSmartBanner t={t} ready={ready} />
{isEditor || isLogin || !isMobileOnly ? <></> : <NavMenu />}
{isEditor || !isMobileOnly ? <></> : <NavMenu />}
<IndicatorLoader />
<ScrollToTop />
<DialogsWrapper t={t} />

View File

@ -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 <StyledMain className="main" {...props} />;
});

View File

@ -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 ? (
<div>
<a className="header-logo-wrapper" href="/">
<img
className="header-logo-min_icon"
src={combineUrl(
AppServerConfig.proxyURL,
"/static/images/nav.logo.react.svg"
)}
/>
<img
className="header-logo-icon"
src={combineUrl(
AppServerConfig.proxyURL,
"/static/images/nav.logo.opened.react.svg"
"/static/images/logo.docspace.react.svg"
)}
/>
</a>
@ -88,8 +82,6 @@ const HeaderUnAuth = ({
) : (
<></>
)}
<div>{enableAdmMess && !wizardToken && <RecoverAccess t={t} />}</div>
</Box>
</Header>
);

View File

@ -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 (
<>
<Box
widthProp="100%"
heightProp="100%"
displayProp="flex"
justifyContent="flex-end"
alignItems="center"
>
<RecoverContainer
heightProp="100%"
displayProp="flex"
onClick={onRecoverClick}
>
<Box paddingProp="12px 8px 12px 12px" className="recover-icon">
<StyledUnionIcon />
</Box>
<Box
paddingProp="14px 12px 14px 0px"
className="recover-text"
widthProp="100%"
>
<Text color="#fff" isBold={true}>
{t("RecoverAccess")}
</Text>
</Box>
</RecoverContainer>
</Box>
{visible && (
<RecoverAccessModalDialog
visible={visible}
loading={loading}
email={email}
emailErr={emailErr}
description={description}
descErr={descErr}
t={t}
onChangeEmail={onChangeEmail}
onChangeDescription={onChangeDescription}
onRecoverModalClose={onRecoverModalClose}
onSendRecoverRequest={onSendRecoverRequest}
/>
)}
</>
);
};
RecoverAccess.propTypes = {
t: PropTypes.func.isRequired,
};
export default RecoverAccess;

View File

@ -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 <StyledWrapper className={className}>{children}</StyledWrapper>;
};
export default ConfirmWrapper;

View File

@ -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 (
<Switch>
<ConfirmRoute
forUnauthorized
path={`${path}/LinkInvite`}
component={CreateUserForm}
/>
<ConfirmRoute
forUnauthorized
path={`${path}/Activation`}
component={ActivateUserForm}
/>
<ConfirmRoute
exact
path={`${path}/EmailActivation`}
component={ActivateEmailForm}
/>
<ConfirmRoute
exact
path={`${path}/EmailChange`}
component={ChangeEmailForm}
/>
<ConfirmRoute
forUnauthorized
path={`${path}/PasswordChange`}
component={ChangePasswordForm}
/>
<ConfirmRoute
exact
path={`${path}/ProfileRemove`}
component={ProfileRemoveForm}
/>
<ConfirmRoute
exact
path={`${path}/PhoneActivation`}
component={ChangePhoneForm}
/>
<ConfirmRoute
exact
path={`${path}/PortalOwnerChange`}
component={ChangeOwnerForm}
/>
<ConfirmRoute exact path={`${path}/TfaAuth`} component={TfaAuthForm} />
<ConfirmRoute
exact
path={`${path}/TfaActivation`}
component={TfaActivationForm}
/>
<ConfirmWrapper className="with-background-pattern">
<Switch>
<ConfirmRoute
forUnauthorized
path={`${path}/LinkInvite`}
component={CreateUserForm}
/>
<ConfirmRoute
forUnauthorized
path={`${path}/Activation`}
component={ActivateUserForm}
/>
<ConfirmRoute
exact
path={`${path}/EmailActivation`}
component={ActivateEmailForm}
/>
<ConfirmRoute
exact
path={`${path}/EmailChange`}
component={ChangeEmailForm}
/>
<ConfirmRoute
forUnauthorized
path={`${path}/PasswordChange`}
component={ChangePasswordForm}
/>
<ConfirmRoute
exact
path={`${path}/ProfileRemove`}
component={ProfileRemoveForm}
/>
<ConfirmRoute
exact
path={`${path}/PhoneActivation`}
component={ChangePhoneForm}
/>
<ConfirmRoute
exact
path={`${path}/PortalOwnerChange`}
component={ChangeOwnerForm}
/>
<ConfirmRoute exact path={`${path}/TfaAuth`} component={TfaAuthForm} />
<ConfirmRoute
exact
path={`${path}/TfaActivation`}
component={TfaActivationForm}
/>
{/* <Route component={Error404} /> */}
</Switch>
{/* <Route component={Error404} /> */}
</Switch>
</ConfirmWrapper>
);
};

View File

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

View File

@ -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) => {
<StyledPage>
<StyledBody>
<StyledHeader>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<FormWrapper>
<Text className="subtitle">
{t("ConfirmOwnerPortalTitle", { newOwner: "NEW OWNER" })}
</Text>
</StyledHeader>
<ButtonsWrapper>
<Button
className="button"
primary
size="normal"
label={t("Common:SaveButton")}
tabIndex={2}
isDisabled={false}
//onClick={this.onAcceptClick} // call toast with t("ConfirmOwnerPortalSuccessMessage")
/>
<Button
className="button"
size="normal"
label={t("Common:CancelButton")}
tabIndex={2}
isDisabled={false}
//onClick={this.onCancelClick}
/>
</ButtonsWrapper>
<ButtonsWrapper>
<Button
primary
scale
size="medium"
label={t("Common:SaveButton")}
tabIndex={2}
isDisabled={false}
//onClick={this.onAcceptClick} // call toast with t("ConfirmOwnerPortalSuccessMessage")
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={2}
isDisabled={false}
//onClick={this.onCancelClick}
/>
</ButtonsWrapper>
</FormWrapper>
</StyledBody>
</StyledPage>
);

View File

@ -9,10 +9,12 @@ import FieldContainer from "@docspace/components/field-container";
import { inject, observer } from "mobx-react";
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
import withLoader from "../withLoader";
import { getPasswordErrorMessage } from "SRC_DIR/helpers/utils";
import { getPasswordErrorMessage } from "../../../helpers/utils";
import { createPasswordHash } from "@docspace/common/utils";
import tryRedirectTo from "@docspace/common/utils/tryRedirectTo";
import toastr from "@docspace/components/toast/toastr";
import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
const ChangePasswordForm = (props) => {
const {
@ -81,63 +83,66 @@ const ChangePasswordForm = (props) => {
<StyledPage>
<StyledBody>
<StyledHeader>
<Text fontSize="23px" fontWeight="700">
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<div className="password-change-form">
<Text className="confirm-subtitle">{t("PassworResetTitle")}</Text>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
className="confirm-input"
simpleView={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
inputValue={password}
<FormWrapper>
<div className="password-form">
<Text fontSize="16px" fontWeight="600" className="subtitle">
{t("PassworResetTitle")}
</Text>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
size="large"
scale={true}
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onValidateInput={onValidatePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
settings ? settings.minLength : 8
}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
/>
</FieldContainer>
</div>
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
simpleView={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
inputValue={password}
hasError={isPasswordErrorShow && !passwordValid}
size="large"
scale
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onValidateInput={onValidatePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
settings ? settings.minLength : 8
}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
/>
</FieldContainer>
</div>
<Button
className="confirm-button"
primary
size="normal"
label={t("Common:Create")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
<Button
primary
size="medium"
scale
label={t("Common:Create")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</FormWrapper>
</StyledBody>
</StyledPage>
);

View File

@ -8,6 +8,8 @@ import Section from "@docspace/common/components/Section";
import { inject, observer } from "mobx-react";
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
import withLoader from "../withLoader";
import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
const ChangePhoneForm = (props) => {
const { t, greetingTitle } = props;
@ -17,39 +19,45 @@ const ChangePhoneForm = (props) => {
<StyledPage>
<StyledBody>
<StyledHeader>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
<Text fontSize="16px" fontWeight="600" className="confirm-subtitle">
{t("EnterPhone")}
</Text>
<Text>
{t("CurrentNumber")}: {currentNumber}
</Text>
<Text>{t("PhoneSubtitle")}</Text>
</StyledHeader>
<TextInput
className="phone-input"
id="phone"
name="phone"
type="phone"
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
hasError={false}
guide={false}
/>
<FormWrapper>
<div className="subtitle">
<Text fontSize="16px" fontWeight="600" className="phone-title">
{t("EnterPhone")}
</Text>
<Text>
{t("CurrentNumber")}: {currentNumber}
</Text>
<Text>{t("PhoneSubtitle")}</Text>
</div>
<Button
className="confirm-button"
primary
size="normal"
label={t("GetCode")}
tabIndex={2}
isDisabled={false}
/>
<TextInput
className="phone-input"
id="phone"
name="phone"
type="phone"
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
hasError={false}
guide={false}
/>
<Button
primary
scale
size="medium"
label={t("GetCode")}
tabIndex={2}
isDisabled={false}
/>
</FormWrapper>
</StyledBody>
</StyledPage>
);

View File

@ -14,7 +14,6 @@ import PasswordInput from "@docspace/components/password-input";
import FieldContainer from "@docspace/components/field-container";
import toastr from "@docspace/components/toast/toastr";
import SocialButton from "@docspace/components/social-button";
//import FacebookButton from "@docspace/components/facebook-button";
import {
getAuthProviders,
getCapabilities,
@ -30,16 +29,15 @@ import withLoader from "../withLoader";
import MoreLoginModal from "login/moreLogin";
import AppLoader from "@docspace/common/components/AppLoader";
import EmailInput from "@docspace/components/email-input";
import { getPasswordErrorMessage } from "SRC_DIR/helpers/utils";
import { hugeMobile, tablet } from "@docspace/components/utils/device";
import { getPasswordErrorMessage } from "../../../helpers/utils";
import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
export const ButtonsWrapper = styled.div`
display: flex;
flex-direction: column;
width: 320px;
@media (max-width: 768px) {
width: 100%;
}
width: 100%;
.buttonWrapper {
margin-bottom: 8px;
@ -48,15 +46,29 @@ export const ButtonsWrapper = styled.div`
`;
const ConfirmContainer = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
gap: 70px;
align-items: center;
margin: 80px auto 0 auto;
max-width: 960px;
display: flex;
flex: 1fr 1fr;
gap: 80px;
flex-direction: row;
justify-content: center;
margin-top: 80px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
@media ${tablet} {
margin: 100px auto 0 auto;
display: flex;
flex: 1fr;
flex-direction: column;
align-items: center;
gap: 80px;
}
@media ${hugeMobile} {
margin-top: 32px;
width: 100%;
flex: 1fr;
flex-direction: column;
gap: 80px;
padding-right: 8px;
}
`;
@ -71,9 +83,17 @@ const GreetingContainer = styled.div`
display: ${(props) => !props.isGreetingMode && "none"};
}
@media ${hugeMobile} {
width: 100%;
}
.greeting-title {
width: 100%;
padding-bottom: 32px;
@media ${tablet} {
text-align: center;
}
}
.greeting-block {
@ -107,6 +127,21 @@ const GreetingContainer = styled.div`
padding: 16px;
width: 100%;
}
.docspace-logo {
padding-bottom: 32px;
.injected-svg {
height: 44px;
}
@media ${tablet} {
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 64px;
}
}
`;
const RegisterContainer = styled.div`
@ -114,6 +149,7 @@ const RegisterContainer = styled.div`
flex-direction: column;
align-items: center;
height: 100%;
width: 100%;
.or-label {
margin: 0 8px;
@ -125,18 +161,10 @@ const RegisterContainer = styled.div`
.line {
display: flex;
width: 320px;
width: 100%;
align-items: center;
color: #eceef1;
padding-top: 35px;
@media (max-width: 768px) {
width: 480px;
}
@media (max-width: 414px) {
width: 311px;
}
}
.line:before,
@ -152,7 +180,7 @@ const RegisterContainer = styled.div`
.auth-form-container {
margin-top: 32px;
width: 320px;
width: 100%;
.form-field {
height: 48px;
@ -527,6 +555,7 @@ const Confirm = (props) => {
return (
<ConfirmContainer>
<GreetingContainer isGreetingMode={isGreetingMode}>
<DocspaceLogo className="docspace-logo" />
<Text
fontSize="23px"
fontWeight={700}
@ -553,162 +582,183 @@ const Confirm = (props) => {
</div>
</GreetingContainer>
<RegisterContainer isGreetingMode={isGreetingMode}>
{ssoExists() && <ButtonsWrapper>{ssoButton()}</ButtonsWrapper>}
<FormWrapper>
<RegisterContainer isGreetingMode={isGreetingMode}>
{ssoExists() && <ButtonsWrapper>{ssoButton()}</ButtonsWrapper>}
{oauthDataExists() && (
<>
<ButtonsWrapper>{providerButtons()}</ButtonsWrapper>
{providers && providers.length > 2 && (
<Link
isHovered
type="action"
fontSize="13px"
fontWeight="600"
color="#3B72A7"
className="more-label"
onClick={moreAuthOpen}
>
{t("Common:ShowMore")}
</Link>
)}
</>
)}
{oauthDataExists() && (
<>
<ButtonsWrapper>{providerButtons()}</ButtonsWrapper>
{providers && providers.length > 2 && (
<Link
isHovered
type="action"
fontSize="13px"
fontWeight="600"
color="#3B72A7"
className="more-label"
onClick={moreAuthOpen}
>
{t("Common:ShowMore")}
</Link>
)}
</>
)}
{(oauthDataExists() || ssoExists()) && (
<div className="line">
<Text color="#A3A9AE" className="or-label">
{t("Or")}
</Text>
</div>
)}
{(oauthDataExists() || ssoExists()) && (
<div className="line">
<Text color="#A3A9AE" className="or-label">
{t("Or")}
</Text>
</div>
)}
<form className="auth-form-container">
<div className="auth-form-fields">
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isEmailErrorShow && !emailValid}
errorMessage={
emailErrorText
? t(`Common:${emailErrorText}`)
: t("Common:RequiredField")
}
>
<EmailInput
id="login"
name="login"
type="email"
<form className="auth-form-container">
<div className="auth-form-fields">
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isEmailErrorShow && !emailValid}
value={email}
placeholder={t("Common:Email")}
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="username"
onChange={onChangeEmail}
onBlur={onBlurEmail}
onValidateInput={onValidateEmail}
forwardedRef={inputRef}
/>
</FieldContainer>
errorMessage={
emailErrorText
? t(`Common:${emailErrorText}`)
: t("Common:RequiredField")
}
>
<EmailInput
id="login"
name="login"
type="email"
hasError={isEmailErrorShow && !emailValid}
value={email}
placeholder={t("Common:Email")}
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="username"
onChange={onChangeEmail}
onBlur={onBlurEmail}
onValidateInput={onValidateEmail}
forwardedRef={inputRef}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!fnameValid}
errorMessage={errorText ? errorText : t("Common:RequiredField")}
>
<TextInput
id="first-name"
name="first-name"
type="text"
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!fnameValid}
value={fname}
placeholder={t("FirstName")}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
onChange={onChangeFname}
onKeyDown={onKeyPress}
/>
</FieldContainer>
errorMessage={errorText ? errorText : t("Common:RequiredField")}
>
<TextInput
id="first-name"
name="first-name"
type="text"
hasError={!fnameValid}
value={fname}
placeholder={t("FirstName")}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
onChange={onChangeFname}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!snameValid}
errorMessage={errorText ? errorText : t("Common:RequiredField")}
>
<TextInput
id="last-name"
name="last-name"
type="text"
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!snameValid}
value={sname}
placeholder={t("Common:LastName")}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
onChange={onChangeSname}
onKeyDown={onKeyPress}
/>
</FieldContainer>
errorMessage={errorText ? errorText : t("Common:RequiredField")}
>
<TextInput
id="last-name"
name="last-name"
type="text"
hasError={!snameValid}
value={sname}
placeholder={t("Common:LastName")}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
onChange={onChangeSname}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
simpleView={false}
hideNewPasswordButton
showCopyLink={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
inputValue={password}
size="large"
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
simpleView={false}
hideNewPasswordButton
showCopyLink={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
hasError={isPasswordErrorShow && !passwordValid}
inputValue={password}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="current-password"
onChange={onChangePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
onValidateInput={onValidatePassword}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t(
"Common:PasswordMinimumLength"
)}: ${settings ? settings.minLength : 8}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t(
"Common:PasswordLimitUpperCase"
)}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
/>
</FieldContainer>
<Button
id="submit"
className="login-button"
primary
size="medium"
scale={true}
label={
isLoading
? t("Common:LoadingProcessing")
: t("LoginRegistryButton")
}
tabIndex={1}
isDisabled={isLoading}
autoComplete="current-password"
onChange={onChangePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
onValidateInput={onValidatePassword}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
settings ? settings.minLength : 8
}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
isLoading={isLoading}
onClick={onSubmit}
/>
</FieldContainer>
</div>
<Button
id="submit"
className="login-button"
className="login-button is-greeting-mode-button"
primary
size="normal"
size="medium"
scale={true}
label={
isLoading
@ -718,38 +768,21 @@ const Confirm = (props) => {
tabIndex={1}
isDisabled={isLoading}
isLoading={isLoading}
onClick={onSubmit}
onClick={onGreetingSubmit}
/>
</div>
</form>
<Button
id="submit"
className="login-button is-greeting-mode-button"
primary
size="normal"
scale={true}
label={
isLoading
? t("Common:LoadingProcessing")
: t("LoginRegistryButton")
}
tabIndex={1}
isDisabled={isLoading}
isLoading={isLoading}
onClick={onGreetingSubmit}
<MoreLoginModal
t={t}
visible={moreAuthVisible}
onClose={moreAuthClose}
providers={providers}
onSocialLoginClick={onSocialButtonClick}
ssoLabel={ssoLabel}
ssoUrl={ssoUrl}
/>
</form>
<MoreLoginModal
t={t}
visible={moreAuthVisible}
onClose={moreAuthClose}
providers={providers}
onSocialLoginClick={onSocialButtonClick}
ssoLabel={ssoLabel}
ssoUrl={ssoUrl}
/>
</RegisterContainer>
</RegisterContainer>
</FormWrapper>
</ConfirmContainer>
);
};

View File

@ -9,6 +9,8 @@ import { deleteSelf } from "@docspace/common/api/people";
import toastr from "@docspace/components/toast/toastr";
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
import withLoader from "../withLoader";
import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
const ProfileRemoveForm = (props) => {
const { t, greetingTitle, linkData, logout } = props;
@ -35,6 +37,7 @@ const ProfileRemoveForm = (props) => {
<StyledPage>
<StyledBody>
<StyledHeader>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{t("DeleteProfileSuccessMessage")}
</Text>
@ -51,26 +54,34 @@ const ProfileRemoveForm = (props) => {
<StyledPage>
<StyledBody>
<StyledHeader>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
<Text fontSize="16px" fontWeight="600" className="confirm-subtitle">
{t("DeleteProfileConfirmation")}
</Text>
<Text className="info-delete">
{t("DeleteProfileConfirmationInfo")}
</Text>
</StyledHeader>
<Button
className="confirm-button"
primary
size="normal"
label={t("DeleteProfileBtn")}
tabIndex={1}
isDisabled={isLoading}
onClick={onDeleteProfile}
/>
<FormWrapper>
<div className="subtitle">
<Text
fontSize="16px"
fontWeight="600"
className="delete-profile-confirm"
>
{t("DeleteProfileConfirmation")}
</Text>
<Text>{t("DeleteProfileConfirmationInfo")}</Text>
</div>
<Button
primary
scale
size="medium"
label={t("DeleteProfileBtn")}
tabIndex={1}
isDisabled={isLoading}
onClick={onDeleteProfile}
/>
</FormWrapper>
</StyledBody>
</StyledPage>
);

Some files were not shown because too many files have changed in this diff Show More