diff --git a/build/Jenkinsfile b/build/Jenkinsfile
index 056928b739..f6a9021f08 100644
--- a/build/Jenkinsfile
+++ b/build/Jenkinsfile
@@ -43,20 +43,20 @@ pipeline {
stages {
stage('Components') {
steps {
- sh "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}/packages/asc-web-components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true"
+ sh "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}/packages/components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true"
}
post {
success {
- junit 'packages/asc-web-components/junit.xml'
+ junit 'packages/components/junit.xml'
publishHTML target: [
allowMissing : false,
alwaysLinkToLastBuild: false,
keepAll : true,
- reportDir : 'packages/asc-web-components/coverage/lcov-report',
+ reportDir : 'packages/components/coverage/lcov-report',
reportFiles : 'index.html',
reportName : 'Unix Test Report'
]
- publishCoverage adapters: [coberturaAdapter('packages/asc-web-components/coverage/cobertura-coverage.xml')]
+ publishCoverage adapters: [coberturaAdapter('packages/components/coverage/cobertura-coverage.xml')]
}
}
}
@@ -72,16 +72,16 @@ pipeline {
stages {
stage('Components') {
steps {
- bat "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}\\packages\\asc-web-components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true"
+ bat "yarn install --frozen-lockfile && yarn build && cd ${env.WORKSPACE}\\packages\\components && yarn test:coverage --ci --reporters=default --reporters=jest-junit || true"
}
post {
success {
- junit 'packages\\asc-web-components\\junit.xml'
+ junit 'packages\\components\\junit.xml'
publishHTML target: [
allowMissing : false,
alwaysLinkToLastBuild: false,
keepAll : true,
- reportDir : 'packages\\asc-web-components\\coverage\\lcov-report',
+ reportDir : 'packages\\components\\coverage\\lcov-report',
reportFiles : 'index.html',
reportName : 'Windows Test Report'
]
diff --git a/build/build.backend.bat b/build/build.backend.bat
index 36c58d38e5..a4b3df95b7 100644
--- a/build/build.backend.bat
+++ b/build/build.backend.bat
@@ -10,26 +10,31 @@ if %errorlevel% == 0 (
call start\stop.bat nopause
dotnet build ..\asc.web.slnf /fl1 /flp1:logfile=asc.web.log;verbosity=normal
echo.
+)
+
+if %errorlevel% == 0 (
echo install nodejs projects dependencies...
echo.
-
for /R "scripts\" %%f in (*.bat) do (
echo Run script %%~nxf...
echo.
call scripts\%%~nxf
)
+)
echo.
+if %errorlevel% == 0 (
call start\start.bat nopause
+)
echo.
if "%1"=="nopause" goto end
pause
-)
+
:end
\ No newline at end of file
diff --git a/build/createMigrations.bat b/build/createMigrations.bat
index e5726af891..1f7fcf9ca0 100644
--- a/build/createMigrations.bat
+++ b/build/createMigrations.bat
@@ -1,5 +1,6 @@
@echo "MIGRATIONS"
@echo off
-PUSHD %~dp0..\common\Tools\Migration.Creator
-dotnet run --project Migration.Creator.csproj
\ No newline at end of file
+PUSHD %~dp0..\common\Tools\ASC.Migration.Creator
+dotnet run --project ASC.Migration.Creator.csproj
+pause
\ No newline at end of file
diff --git a/build/install/install.bat b/build/install/install.bat
index 321fb88d69..c78c8e8745 100644
--- a/build/install/install.bat
+++ b/build/install/install.bat
@@ -1,9 +1,11 @@
@echo off
-PUSHD %~dp0..\..
-setlocal EnableDelayedExpansion
+PUSHD %~dp0..
+call runasadmin.bat "%~dpnx0"
if %errorlevel% == 0 (
+ PUSHD %~dp0..\..
+ setlocal EnableDelayedExpansion
for /R "build\run\" %%f in (*.bat) do (
call build\run\%%~nxf
echo service create "Onlyoffice%%~nf"
diff --git a/common/ASC.Api.Core/ASC.Api.Core.csproj b/common/ASC.Api.Core/ASC.Api.Core.csproj
index 482a686f5f..e9697161c6 100644
--- a/common/ASC.Api.Core/ASC.Api.Core.csproj
+++ b/common/ASC.Api.Core/ASC.Api.Core.csproj
@@ -18,6 +18,7 @@
+
diff --git a/common/ASC.Api.Core/Auth/BasicAuthHandler.cs b/common/ASC.Api.Core/Auth/BasicAuthHandler.cs
new file mode 100644
index 0000000000..7693eb272c
--- /dev/null
+++ b/common/ASC.Api.Core/Auth/BasicAuthHandler.cs
@@ -0,0 +1,103 @@
+// (c) Copyright Ascensio System SIA 2010-2022
+//
+// This program is a free software product.
+// You can redistribute it and/or modify it under the terms
+// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
+// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
+// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
+// any third-party rights.
+//
+// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
+// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+//
+// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
+//
+// The interactive user interfaces in modified source and object code versions of the Program must
+// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
+//
+// Pursuant to Section 7(b) of the License you must retain the original Product logo when
+// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
+// trademark law for use of our trademarks.
+//
+// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
+// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
+// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+
+using System.Text;
+using System.Text.RegularExpressions;
+
+using SecurityContext = ASC.Core.SecurityContext;
+
+namespace ASC.Api.Core.Auth;
+
+[Scope]
+public class BasicAuthHandler : AuthenticationHandler
+{
+ private readonly UserManager _userManager;
+ private readonly SecurityContext _securityContext;
+ private readonly PasswordHasher _passwordHasher;
+
+ public BasicAuthHandler(
+ IOptionsMonitor options,
+ ILoggerFactory logger,
+ UrlEncoder encoder,
+ ISystemClock clock
+ ) : base(options, logger, encoder, clock)
+ {
+
+ }
+
+ public BasicAuthHandler(
+ IOptionsMonitor options,
+ ILoggerFactory logger,
+ UrlEncoder encoder,
+ ISystemClock clock,
+ UserManager userManager,
+ SecurityContext securityContext,
+ PasswordHasher passwordHasher) : this(options, logger, encoder, clock)
+ {
+ _userManager = userManager;
+ _securityContext = securityContext;
+ _passwordHasher = passwordHasher;
+ }
+
+ protected override Task HandleAuthenticateAsync()
+ {
+ Response.Headers.Add("WWW-Authenticate", "Basic");
+
+ if (!Request.Headers.ContainsKey("Authorization"))
+ {
+ return Task.FromResult(AuthenticateResult.Fail("Authorization header missing."));
+ }
+
+ // Get authorization key
+ var authorizationHeader = Request.Headers["Authorization"].ToString();
+ var authHeaderRegex = new Regex(@"Basic (.*)");
+
+ if (!authHeaderRegex.IsMatch(authorizationHeader))
+ {
+ return Task.FromResult(AuthenticateResult.Fail("Authorization code not formatted properly."));
+ }
+
+ var authBase64 = Encoding.UTF8.GetString(Convert.FromBase64String(authHeaderRegex.Replace(authorizationHeader, "$1")));
+ var authSplit = authBase64.Split(Convert.ToChar(":"), 2);
+ var authUsername = authSplit[0];
+ var authPassword = authSplit.Length > 1 ? authSplit[1] : throw new Exception("Unable to get password");
+
+ try
+ {
+ var userInfo = _userManager.GetUserByEmail(authUsername);
+ var passwordHash = _passwordHasher.GetClientPassword(authPassword);
+
+ _securityContext.AuthenticateMe(userInfo.Email, passwordHash);
+
+ }
+ catch (Exception)
+ {
+ return Task.FromResult(AuthenticateResult.Fail("The username or password is not correct."));
+ }
+
+ return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, Scheme.Name)));
+ }
+}
diff --git a/common/ASC.Api.Core/Auth/CookieAuthHandler.cs b/common/ASC.Api.Core/Auth/CookieAuthHandler.cs
index 5cc88ad434..33b81fdf1b 100644
--- a/common/ASC.Api.Core/Auth/CookieAuthHandler.cs
+++ b/common/ASC.Api.Core/Auth/CookieAuthHandler.cs
@@ -31,9 +31,9 @@ namespace ASC.Api.Core.Auth;
[Scope]
public class CookieAuthHandler : AuthenticationHandler
{
- private readonly AuthorizationHelper _authorizationHelper;
private readonly SecurityContext _securityContext;
private readonly CookiesManager _cookiesManager;
+ private readonly IHttpContextAccessor _httpContextAccessor;
public CookieAuthHandler(
IOptionsMonitor options,
@@ -42,31 +42,59 @@ public class CookieAuthHandler : AuthenticationHandler options,
- ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock,
- AuthorizationHelper authorizationHelper,
+ public CookieAuthHandler(
+ IOptionsMonitor options,
+ ILoggerFactory logger,
+ UrlEncoder encoder,
+ ISystemClock clock,
SecurityContext securityContext,
- CookiesManager cookiesManager)
+ CookiesManager cookiesManager,
+ IHttpContextAccessor httpContextAccessor)
: this(options, logger, encoder, clock)
{
- _authorizationHelper = authorizationHelper;
_securityContext = securityContext;
_cookiesManager = cookiesManager;
+ _httpContextAccessor = httpContextAccessor;
}
protected override Task HandleAuthenticateAsync()
{
- var result = _authorizationHelper.ProcessBasicAuthorization(out _);
- if (!result)
+ try
{
- _securityContext.Logout();
- _cookiesManager.ClearCookies(CookiesType.AuthKey);
- _cookiesManager.ClearCookies(CookiesType.SocketIO);
+ var authorization = _httpContextAccessor.HttpContext.Request.Cookies["asc_auth_key"] ?? _httpContextAccessor.HttpContext.Request.Headers["Authorization"].ToString();
+
+ if (string.IsNullOrEmpty(authorization))
+ {
+ throw new AuthenticationException(nameof(HttpStatusCode.Unauthorized));
+ }
+
+ authorization = authorization.Trim();
+
+ if (0 <= authorization.IndexOf("Bearer", 0))
+ {
+ authorization = authorization.Substring("Bearer ".Length);
+ }
+
+ if (!_securityContext.AuthenticateMe(authorization))
+ {
+ throw new AuthenticationException(nameof(HttpStatusCode.Unauthorized));
+ }
+
+ }
+ catch (Exception)
+ {
+ return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(nameof(HttpStatusCode.Unauthorized))));
+ }
+ finally
+ {
+ if (!_securityContext.IsAuthenticated)
+ {
+ _securityContext.Logout();
+ _cookiesManager.ClearCookies(CookiesType.AuthKey);
+ _cookiesManager.ClearCookies(CookiesType.SocketIO);
+ }
}
- return Task.FromResult(
- result ?
- AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)) :
- AuthenticateResult.Fail(new AuthenticationException(nameof(HttpStatusCode.Unauthorized))));
+ return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, Scheme.Name)));
}
}
\ No newline at end of file
diff --git a/common/ASC.Api.Core/Core/BaseStartup.cs b/common/ASC.Api.Core/Core/BaseStartup.cs
index 1b747cdc60..276446c7fe 100644
--- a/common/ASC.Api.Core/Core/BaseStartup.cs
+++ b/common/ASC.Api.Core/Core/BaseStartup.cs
@@ -24,6 +24,11 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+using System.IdentityModel.Tokens.Jwt;
+
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.Net.Http.Headers;
+
using JsonConverter = System.Text.Json.Serialization.JsonConverter;
namespace ASC.Api.Core;
@@ -65,6 +70,7 @@ public abstract class BaseStartup
services.AddBaseDbContextPool();
services.AddBaseDbContextPool();
services.AddBaseDbContextPool();
+ services.AddBaseDbContextPool();
services.AddBaseDbContextPool();
services.AddBaseDbContextPool();
services.AddBaseDbContextPool();
@@ -102,13 +108,13 @@ public abstract class BaseStartup
services.AddSingleton(jsonOptions);
DIHelper.AddControllers();
- DIHelper.TryAdd();
DIHelper.TryAdd();
DIHelper.TryAdd();
DIHelper.TryAdd();
DIHelper.TryAdd();
DIHelper.TryAdd();
DIHelper.TryAdd();
+ DIHelper.TryAdd();
DIHelper.TryAdd();
DIHelper.TryAdd();
@@ -147,16 +153,72 @@ public abstract class BaseStartup
config.OutputFormatters.Add(new XmlOutputFormatter());
});
- var authBuilder = services.AddAuthentication("cookie")
- .AddScheme("cookie", a => { });
-
- if (ConfirmAddScheme)
+ var authBuilder = services.AddAuthentication(options =>
{
- authBuilder.AddScheme("confirm", a => { });
- }
+ options.DefaultScheme = "MultiAuthSchemes";
+ options.DefaultChallengeScheme = "MultiAuthSchemes";
+ }).AddScheme(CookieAuthenticationDefaults.AuthenticationScheme, a => { })
+ .AddScheme("Basic", a => { })
+ .AddScheme("confirm", a => { })
+ .AddJwtBearer("Bearer", options =>
+ {
+ options.Authority = _configuration["core:oidc:authority"];
+ options.IncludeErrorDetails = true;
+
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateAudience = false
+ };
+
+ options.Events = new JwtBearerEvents
+ {
+ OnTokenValidated = ctx =>
+ {
+ using var scope = ctx.HttpContext.RequestServices.CreateScope();
+
+ var securityContext = scope.ServiceProvider.GetService();
+
+ var claimUserId = ctx.Principal.FindFirstValue("userId");
+
+ if (String.IsNullOrEmpty(claimUserId))
+ {
+ throw new Exception("Claim 'UserId' is not present in claim list");
+ }
+
+ var userId = new Guid(claimUserId);
+
+ securityContext.AuthenticateMeWithoutCookie(userId, ctx.Principal.Claims.ToList());
+
+ return Task.CompletedTask;
+ }
+ };
+ })
+ .AddPolicyScheme("MultiAuthSchemes", JwtBearerDefaults.AuthenticationScheme, options =>
+ {
+
+ options.ForwardDefaultSelector = context =>
+ {
+ var authorizationHeader = context.Request.Headers[HeaderNames.Authorization].FirstOrDefault();
+
+ if (String.IsNullOrEmpty(authorizationHeader)) return CookieAuthenticationDefaults.AuthenticationScheme;
+
+ if (authorizationHeader.StartsWith("Basic ")) return "Basic";
+
+ if (authorizationHeader.StartsWith("Bearer "))
+ {
+ var token = authorizationHeader.Substring("Bearer ".Length).Trim();
+ var jwtHandler = new JwtSecurityTokenHandler();
+
+ return (jwtHandler.CanReadToken(token) && jwtHandler.ReadJwtToken(token).Issuer.Equals(_configuration["core:oidc:authority"]))
+ ? JwtBearerDefaults.AuthenticationScheme : CookieAuthenticationDefaults.AuthenticationScheme;
+ }
+
+ return CookieAuthenticationDefaults.AuthenticationScheme;
+ };
+ });
services.AddAutoMapper(GetAutoMapperProfileAssemblies());
-
+
if (!_hostEnvironment.IsDevelopment())
{
services.AddStartupTask()
@@ -189,8 +251,6 @@ public abstract class BaseStartup
app.UseCultureMiddleware();
- app.UseDisposeMiddleware();
-
app.UseEndpoints(endpoints =>
{
endpoints.MapCustom();
diff --git a/common/ASC.Api.Core/GlobalUsings.cs b/common/ASC.Api.Core/GlobalUsings.cs
index bb8f3e803d..298e86ee55 100644
--- a/common/ASC.Api.Core/GlobalUsings.cs
+++ b/common/ASC.Api.Core/GlobalUsings.cs
@@ -24,8 +24,6 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
-global using Microsoft.Extensions.DependencyInjection.Extensions;
-global using System.Collections.Concurrent;
global using System.ComponentModel;
global using System.Globalization;
global using System.Linq.Expressions;
@@ -36,6 +34,7 @@ global using System.Runtime.Serialization;
global using System.Security;
global using System.Security.Authentication;
global using System.Security.Claims;
+global using System.Text;
global using System.Text.Encodings.Web;
global using System.Text.Json;
global using System.Text.Json.Serialization;
@@ -56,8 +55,8 @@ global using ASC.AuditTrail.Types;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.DependencyInjection;
+global using ASC.Common.Log;
global using ASC.Common.Logging;
-global using ASC.Common.Mapping;
global using ASC.Common.Notify.Engine;
global using ASC.Common.Threading;
global using ASC.Common.Utils;
@@ -119,11 +118,14 @@ global using Microsoft.AspNetCore.WebUtilities;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
+global using Microsoft.Extensions.DependencyInjection.Extensions;
global using Microsoft.Extensions.Diagnostics.HealthChecks;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options;
global using Microsoft.Extensions.Primitives;
+global using Microsoft.AspNetCore.Authentication.JwtBearer;
+global using Microsoft.IdentityModel.Tokens;
global using Newtonsoft.Json;
global using Newtonsoft.Json.Serialization;
diff --git a/common/ASC.Api.Core/Middleware/CommonApiResponse.cs b/common/ASC.Api.Core/Middleware/CommonApiResponse.cs
index e3ac097a8a..d0de60efc8 100644
--- a/common/ASC.Api.Core/Middleware/CommonApiResponse.cs
+++ b/common/ASC.Api.Core/Middleware/CommonApiResponse.cs
@@ -56,44 +56,64 @@ public class ErrorApiResponse : CommonApiResponse
public class SuccessApiResponse : CommonApiResponse
{
- public int? Count { get; set; }
- public long? Total { get; set; }
+ private readonly HttpContext _httpContext;
+
public object Response { get; set; }
+ public int? Count
+ {
+ get
+ {
+
+ if (_httpContext.Items.TryGetValue("Count", out var count))
+ {
+ return (int?)count;
+ }
+
+ if (Response is List