From d5b009554efb3b07eda6e0d72afd755a326a7339 Mon Sep 17 00:00:00 2001 From: Alexey Bannov Date: Thu, 29 Jun 2023 20:47:47 +0300 Subject: [PATCH] backend: update JWT dll. Added authorize via JWT token --- common/ASC.Api.Core/ASC.Api.Core.csproj | 2 +- .../Auth/AuthorizationExtension.cs | 200 ++++++++++++++++++ .../Auth/AuthorizationPolicyProvider.cs | 57 +++++ common/ASC.Api.Core/Auth/BasicAuthHandler.cs | 10 +- .../ASC.Api.Core/Auth/ConfirmAuthHandler.cs | 5 +- .../Auth/ScopesAuthorizationHandler.cs | 81 +++++++ common/ASC.Api.Core/Auth/ScopesRequirement.cs | 36 ++++ common/ASC.Api.Core/Core/BaseStartup.cs | 49 +---- .../Core/CustomEndpointDataSource.cs | 4 +- common/ASC.Api.Core/Log/BaseStartupLogger.cs | 44 ++++ common/ASC.Common/ASC.Common.csproj | 2 +- .../{Constants.cs => AuthConstants.cs} | 7 +- common/ASC.Common/Utils/JsonWebToken.cs | 2 +- .../Context/Impl/SubscriptionManager.cs | 10 +- .../Context/SecurityContext.cs | 19 +- .../Security/Authorizing/AzManager.cs | 2 +- .../Security/Authorizing/RoleProvider.cs | 6 +- common/ASC.Core.Common/Security/Security.cs | 16 +- .../Security/UserGroupObject.cs | 2 +- .../Security/UserSecurityProvider.cs | 2 +- common/ASC.Core.Common/Users/Constants.cs | 21 +- .../ASC.FederatedLogin.csproj | 2 +- config/nlog.config | 4 +- .../Core/HttpHandlers/FileHandler.ashx.cs | 8 +- .../Server/Api/PeopleControllerBase.cs | 3 +- .../ASC.People/Server/Api/UserController.cs | 13 ++ web/ASC.Web.Studio/Startup.cs | 4 +- 27 files changed, 514 insertions(+), 97 deletions(-) create mode 100644 common/ASC.Api.Core/Auth/AuthorizationExtension.cs create mode 100644 common/ASC.Api.Core/Auth/AuthorizationPolicyProvider.cs create mode 100644 common/ASC.Api.Core/Auth/ScopesAuthorizationHandler.cs create mode 100644 common/ASC.Api.Core/Auth/ScopesRequirement.cs create mode 100644 common/ASC.Api.Core/Log/BaseStartupLogger.cs rename common/ASC.Common/Security/Authorizing/{Constants.cs => AuthConstants.cs} (94%) diff --git a/common/ASC.Api.Core/ASC.Api.Core.csproj b/common/ASC.Api.Core/ASC.Api.Core.csproj index 1be535aed0..8bacc9e7c1 100644 --- a/common/ASC.Api.Core/ASC.Api.Core.csproj +++ b/common/ASC.Api.Core/ASC.Api.Core.csproj @@ -20,7 +20,7 @@ - + diff --git a/common/ASC.Api.Core/Auth/AuthorizationExtension.cs b/common/ASC.Api.Core/Auth/AuthorizationExtension.cs new file mode 100644 index 0000000000..02419c6285 --- /dev/null +++ b/common/ASC.Api.Core/Auth/AuthorizationExtension.cs @@ -0,0 +1,200 @@ +// (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.Collections.Specialized; + +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; + +namespace ASC.Api.Core.Auth; + +public static class AuthorizationExtension +{ + private static readonly NameValueCollection _scopesMap = new NameValueCollection() + { + { "GET api/[0-9].[0-9]/files/rooms", "rooms:read,rooms:write" }, + { "(POST|PUT|DELETE|UPDATE) api/[0-9].[0-9]/files/rooms", "rooms:write" }, + { "GET api/[0-9].[0-9]/files", "files:read,files:write" }, + { "(POST|PUT|DELETE|UPDATE) api/[0-9].[0-9]/files", "files:write" }, + { "GET api/[0-9].[0-9]/people/@self", "account.self:read,account.self:write" }, + { "(POST|PUT|DELETE|UPDATE) api/[0-9].[0-9]/people/@self", "account.self:write" }, + { "GET api/[0-9].[0-9]/people", "accounts:read,accounts:write" }, + { "(POST|PUT|DELETE|UPDATE) api/[0-9].[0-9]/people", "accounts:write" }, + }; + + private static readonly string[] _allScopes = new[] { + "files:read", + "files:write", + "rooms:read", + "rooms:write", + "account.self:read", + "account.self:write", + "accounts:read", + "accounts:write" }; + + private static string GetAuthorizePolicy(string routePattern, string httpMethod) + { + foreach (var regexPattern in _scopesMap.AllKeys) + { + var regex = new Regex(regexPattern); + + if (!regex.IsMatch($"{httpMethod} {routePattern}")) continue; + + var scopes = _scopesMap[regexPattern]; + + return scopes; + } + + return null; + } + + public static IServiceCollection AddJwtBearerAuthentication(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + + services.AddAuthentication() + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => + { +#if DEBUG + + options.IncludeErrorDetails = true; +#endif + options.Configuration = new OpenIdConnectConfiguration(); + + IdentityModelEventSource.ShowPII = true; + options.MapInboundClaims = false; + + options.TokenValidationParameters.RoleClaimType = "role"; + options.TokenValidationParameters.NameClaimType = "name"; + + options.TokenValidationParameters = new TokenValidationParameters + { + // Clock skew compensates for server time drift. + // We recommend 5 minutes or less: + ClockSkew = TimeSpan.FromMinutes(5), + // Specify the key used to sign the token: + // IssuerSigningKey = signingKey, + RequireSignedTokens = false, + RequireAudience = false, + // Ensure the token hasn't expired: + RequireExpirationTime = false, + ValidateLifetime = false, + // Ensure the token audience matches our audience value (default true): + ValidateAudience = false, + ValidAudience = "4testing", + // Ensure the token was issued by a trusted authorization server (default true): + ValidateIssuer = false, + // ValidIssuer = "https://{yourOktaDomain}/oauth2/default", + ValidateIssuerSigningKey = false, + SignatureValidator = delegate (string token, TokenValidationParameters parameters) + { + var jwt = new JwtSecurityToken(token); + + return jwt; + } + }; + + options.Events = new JwtBearerEvents + { + OnTokenValidated = async ctx => + { + using var scope = ctx.HttpContext.RequestServices.CreateScope(); + + var securityContext = scope.ServiceProvider.GetService(); + + var logger = scope.ServiceProvider.GetService>(); + + logger.DebugOnTokenValidatedCallback(); + + if (ctx?.Principal != null) + { + foreach (var claim in ctx.Principal.Claims) + { + logger.DebugOnTokenValidatedCallback(claim.Type, claim.Value); + } + } + + var claimSid = ctx.Principal.Claims.FirstOrDefault(x => x.Type == "userId"); + + if (claimSid == null || !Guid.TryParse(claimSid.Value, out var userId)) + { + throw new AuthenticationException($"Claim 'Sid' is not present in JWT"); + } + + await securityContext.AuthenticateMeWithoutCookieAsync(userId, ctx.Principal.Claims.ToList()); + }, + OnMessageReceived = msg => + { + using var scope = msg?.HttpContext.RequestServices.CreateScope(); + + var logger = scope?.ServiceProvider.GetService>(); + + var token = msg?.Request.Headers.Authorization.ToString(); + string path = msg?.Request.Path ?? ""; + + logger.DebugOnMessageReceivedCallback(path); + + if (!string.IsNullOrEmpty(token)) + { + logger.DebugOnMessageReceivedCallbackAccessToken(token); + } + else + { + logger.DebugOnMessageReceivedCallbackNoAccessToken(); + } + + return Task.CompletedTask; + } + }; + }); + + + return services; + + } + + public static TBuilder WithRequirementAuthorization(this TBuilder builder) where TBuilder : IEndpointConventionBuilder + { + builder.Add(endpointBuilder => + { + var httpMethodMetadata = endpointBuilder.Metadata.OfType().FirstOrDefault(); + var authorizeAttribute = endpointBuilder.Metadata.OfType().FirstOrDefault(); + var httpMethod = httpMethodMetadata?.HttpMethods.FirstOrDefault(); + + var authorizePolicy = GetAuthorizePolicy(((RouteEndpointBuilder)endpointBuilder).RoutePattern.RawText, httpMethod); + + if (authorizeAttribute == null && authorizePolicy != null) + { + authorizeAttribute = new AuthorizeAttribute(authorizePolicy); + + endpointBuilder.Metadata.Add(authorizeAttribute); + } + }); + + return builder; + } +} diff --git a/common/ASC.Api.Core/Auth/AuthorizationPolicyProvider.cs b/common/ASC.Api.Core/Auth/AuthorizationPolicyProvider.cs new file mode 100644 index 0000000000..dea088c4ef --- /dev/null +++ b/common/ASC.Api.Core/Auth/AuthorizationPolicyProvider.cs @@ -0,0 +1,57 @@ +// (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.Auth; +public class AuthorizationPolicyProvider : IAuthorizationPolicyProvider +{ + public AuthorizationPolicyProvider(IOptions options) + { + FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); + } + + public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } + + public Task GetDefaultPolicyAsync() + { + var basePolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser(); + + basePolicy.AddRequirements(new ScopesRequirement(AuthConstants.Claim_ScopeRootWrite.Value)); + + return Task.FromResult(basePolicy.Build()); + } + + public Task GetFallbackPolicyAsync() => FallbackPolicyProvider.GetFallbackPolicyAsync(); + + public Task GetPolicyAsync(string policyName) + { + var policy = new AuthorizationPolicyBuilder(); + + policy.AddRequirements(new ScopesRequirement(policyName)); + + return Task.FromResult(policy.Build()); + } +} \ No newline at end of file diff --git a/common/ASC.Api.Core/Auth/BasicAuthHandler.cs b/common/ASC.Api.Core/Auth/BasicAuthHandler.cs index c70bb1c8fb..4e0a453101 100644 --- a/common/ASC.Api.Core/Auth/BasicAuthHandler.cs +++ b/common/ASC.Api.Core/Auth/BasicAuthHandler.cs @@ -85,10 +85,14 @@ public class BasicAuthHandler : AuthenticationHandler() + { + AuthConstants.Claim_ScopeRootWrite + }; + await _securityContext.AuthenticateMeAsync(userInfo.Email, passwordHash, null, claims); } catch (Exception) { diff --git a/common/ASC.Api.Core/Auth/ConfirmAuthHandler.cs b/common/ASC.Api.Core/Auth/ConfirmAuthHandler.cs index 822fd7f5e2..18c37fafca 100644 --- a/common/ASC.Api.Core/Auth/ConfirmAuthHandler.cs +++ b/common/ASC.Api.Core/Auth/ConfirmAuthHandler.cs @@ -81,9 +81,10 @@ public class ConfirmAuthHandler : AuthenticationHandler() { - new Claim(ClaimTypes.Role, emailValidationKeyModel.Type.ToString()) + new Claim(ClaimTypes.Role, emailValidationKeyModel.Type.ToString()), + AuthConstants.Claim_ScopeRootWrite }; - + if (checkKeyResult == EmailValidationKeyProvider.ValidationResult.Ok) { Guid userId; diff --git a/common/ASC.Api.Core/Auth/ScopesAuthorizationHandler.cs b/common/ASC.Api.Core/Auth/ScopesAuthorizationHandler.cs new file mode 100644 index 0000000000..0a5e565ee2 --- /dev/null +++ b/common/ASC.Api.Core/Auth/ScopesAuthorizationHandler.cs @@ -0,0 +1,81 @@ +// (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.Auth; + +public class ScopesAuthorizationHandler : AuthorizationHandler +{ + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ScopesRequirement requirement) + { + if (!context.User.Identity.IsAuthenticated) + { + return Task.CompletedTask; + } + + if (context.HasSucceeded) + { + return Task.CompletedTask; + } + + if (context.User == null || requirement == null || string.IsNullOrWhiteSpace(requirement.Scopes)) + { + return Task.CompletedTask; + } + + var requirementScopes = requirement.Scopes.Split(",", StringSplitOptions.RemoveEmptyEntries); + + if (requirementScopes?.Any() != true) + { + return Task.CompletedTask; + } + + var expectedRequirements = requirementScopes.ToList(); + + if (expectedRequirements.Count == 0) + { + return Task.CompletedTask; + } + + var userScopeClaims = context.User.Claims?.Where(c => string.Equals(c.Type, "scope", StringComparison.OrdinalIgnoreCase)); + + foreach (var claim in userScopeClaims ?? Enumerable.Empty()) + { + var match = expectedRequirements + .Where(r => string.Equals(r, claim.Value, StringComparison.OrdinalIgnoreCase) || + string.Equals(AuthConstants.Claim_ScopeRootWrite.Value, claim.Value, StringComparison.OrdinalIgnoreCase)); + + if (match.Any()) + { + context.Succeed(requirement); + + break; + } + } + + return Task.CompletedTask; + } +} + diff --git a/common/ASC.Api.Core/Auth/ScopesRequirement.cs b/common/ASC.Api.Core/Auth/ScopesRequirement.cs new file mode 100644 index 0000000000..4d2e1f85be --- /dev/null +++ b/common/ASC.Api.Core/Auth/ScopesRequirement.cs @@ -0,0 +1,36 @@ +// (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.Auth; +public class ScopesRequirement : IAuthorizationRequirement +{ + public string Scopes { get; } + + public ScopesRequirement(string scopes) + { + Scopes = scopes ?? throw new ArgumentNullException(nameof(scopes)); + } +} \ 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 3790b52911..01ef3815b5 100644 --- a/common/ASC.Api.Core/Core/BaseStartup.cs +++ b/common/ASC.Api.Core/Core/BaseStartup.cs @@ -199,45 +199,14 @@ public abstract class BaseStartup config.Filters.Add(new CustomExceptionFilterAttribute()); config.Filters.Add(new TypeFilterAttribute(typeof(WebhooksGlobalFilterAttribute))); }); - - var authBuilder = services.AddAuthentication(options => + + services.AddAuthentication(options => { options.DefaultScheme = MultiAuthSchemes; options.DefaultChallengeScheme = MultiAuthSchemes; }).AddScheme(CookieAuthenticationDefaults.AuthenticationScheme, a => { }) .AddScheme(BasicAuthScheme, 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 = async 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); - - await securityContext.AuthenticateMeWithoutCookieAsync(userId, ctx.Principal.Claims.ToList()); - } - }; - }) .AddPolicyScheme(MultiAuthSchemes, JwtBearerDefaults.AuthenticationScheme, options => { options.ForwardDefaultSelector = context => @@ -261,11 +230,7 @@ public abstract class BaseStartup if (jwtHandler.CanReadToken(token)) { - var issuer = jwtHandler.ReadJwtToken(token).Issuer; - if (!string.IsNullOrEmpty(issuer) && issuer.Equals(_configuration["core:oidc:authority"])) - { - return JwtBearerDefaults.AuthenticationScheme; - } + return JwtBearerDefaults.AuthenticationScheme; } } @@ -273,6 +238,8 @@ public abstract class BaseStartup }; }); + services.AddJwtBearerAuthentication(); + services.AddAutoMapper(GetAutoMapperProfileAssemblies()); if (!_hostEnvironment.IsDevelopment()) @@ -305,16 +272,16 @@ public abstract class BaseStartup app.UseSynchronizationContextMiddleware(); - app.UseAuthentication(); - + app.UseAuthorization(); app.UseAuthorization(); app.UseCultureMiddleware(); app.UseLoggerMiddleware(); - + app.UseEndpoints(async endpoints => { + await endpoints.MapCustomAsync(WebhooksEnabled, app.ApplicationServices); endpoints.MapHealthChecks("/health", new HealthCheckOptions() diff --git a/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs b/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs index 2338a98aa4..8a98150df0 100644 --- a/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs +++ b/common/ASC.Api.Core/Core/CustomEndpointDataSource.cs @@ -25,7 +25,6 @@ // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode namespace ASC.Api.Core.Core; - public class CustomEndpointDataSource : EndpointDataSource { private readonly EndpointDataSource _source; @@ -77,7 +76,8 @@ public static class EndpointExtension { public static async Task MapCustomAsync(this IEndpointRouteBuilder endpoints, bool webhooksEnabled = false, IServiceProvider serviceProvider = null) { - endpoints.MapControllers().RequireAuthorization(); + endpoints.MapControllers() + .WithRequirementAuthorization(); if (webhooksEnabled && serviceProvider != null) { diff --git a/common/ASC.Api.Core/Log/BaseStartupLogger.cs b/common/ASC.Api.Core/Log/BaseStartupLogger.cs new file mode 100644 index 0000000000..034666efb1 --- /dev/null +++ b/common/ASC.Api.Core/Log/BaseStartupLogger.cs @@ -0,0 +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.Log; +internal static partial class BaseStartupLogger +{ + [LoggerMessage(Level = LogLevel.Debug, Message = "OnTokenValidatedCallback: Claims from the access token")] + public static partial void DebugOnTokenValidatedCallback(this ILogger logger); + + [LoggerMessage(Level = LogLevel.Debug, Message = "OnTokenValidatedCallback: {claimType} - {claimValue}")] + public static partial void DebugOnTokenValidatedCallback(this ILogger logger, string claimType, string claimValue); + + [LoggerMessage(Level = LogLevel.Debug, Message = "OnMessageReceived: Access token from {url}")] + public static partial void DebugOnMessageReceivedCallback(this ILogger logger, string url); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Access token: {token}")] + public static partial void DebugOnMessageReceivedCallbackAccessToken(this ILogger logger, String token); + + [LoggerMessage(Level = LogLevel.Debug, Message = "Token: No access token provided")] + public static partial void DebugOnMessageReceivedCallbackNoAccessToken(this ILogger logger); +} diff --git a/common/ASC.Common/ASC.Common.csproj b/common/ASC.Common/ASC.Common.csproj index c84ddae3cb..c57ca0ab98 100644 --- a/common/ASC.Common/ASC.Common.csproj +++ b/common/ASC.Common/ASC.Common.csproj @@ -36,7 +36,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/common/ASC.Common/Security/Authorizing/Constants.cs b/common/ASC.Common/Security/Authorizing/AuthConstants.cs similarity index 94% rename from common/ASC.Common/Security/Authorizing/Constants.cs rename to common/ASC.Common/Security/Authorizing/AuthConstants.cs index 9654f48651..98a1933f1d 100644 --- a/common/ASC.Common/Security/Authorizing/Constants.cs +++ b/common/ASC.Common/Security/Authorizing/AuthConstants.cs @@ -24,9 +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.Security.Claims; + namespace ASC.Common.Security.Authorizing; -public static class Constants +public static class AuthConstants { public static readonly Role DocSpaceAdmin = new Role(new Guid("cd84e66b-b803-40fc-99f9-b2969a54a1de"), "DocSpaceAdmin"); public static readonly Role Everyone = new Role(new Guid("c5cc67d1-c3e8-43c0-a3ad-3928ae3e5b5e"), "Everyone"); @@ -36,4 +38,7 @@ public static class Constants public static readonly Role Member = new Role(new Guid("ba74ca02-873f-43dc-8470-8620c156bc67"), "Member"); public static readonly Role Owner = new Role(new Guid("bba32183-a14d-48ed-9d39-c6b4d8925fbf"), "Owner"); public static readonly Role Self = new Role(new Guid("5d5b7260-f7f7-49f1-a1c9-95fbb6a12604"), "Self"); + + public static readonly Claim Claim_ScopeRootWrite = new Claim("scope", "root_write"); + } diff --git a/common/ASC.Common/Utils/JsonWebToken.cs b/common/ASC.Common/Utils/JsonWebToken.cs index 18b5409087..65e350b239 100644 --- a/common/ASC.Common/Utils/JsonWebToken.cs +++ b/common/ASC.Common/Utils/JsonWebToken.cs @@ -60,7 +60,7 @@ public static class JsonWebToken } } -public class DictionaryStringObjectJsonConverter : JsonConverter> +public class DictionaryStringObjectJsonConverter : System.Text.Json.Serialization.JsonConverter> { public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/common/ASC.Core.Common/Context/Impl/SubscriptionManager.cs b/common/ASC.Core.Common/Context/Impl/SubscriptionManager.cs index 25e87c02e2..675660d8dd 100644 --- a/common/ASC.Core.Common/Context/Impl/SubscriptionManager.cs +++ b/common/ASC.Core.Common/Context/Impl/SubscriptionManager.cs @@ -24,7 +24,7 @@ // 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 Constants = ASC.Common.Security.Authorizing.Constants; +using AuthConstants = ASC.Common.Security.Authorizing.AuthConstants; namespace ASC.Core; @@ -37,10 +37,10 @@ public class SubscriptionManager private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); public static readonly List Groups = Groups = new List { - Constants.DocSpaceAdmin.ID, - Constants.Everyone.ID, - Constants.RoomAdmin.ID, - Constants.Collaborator.ID, + AuthConstants.DocSpaceAdmin.ID, + AuthConstants.Everyone.ID, + AuthConstants.RoomAdmin.ID, + AuthConstants.Collaborator.ID, }; public SubscriptionManager(CachedSubscriptionService service, TenantManager tenantManager, ICache cache) diff --git a/common/ASC.Core.Common/Context/SecurityContext.cs b/common/ASC.Core.Common/Context/SecurityContext.cs index e3f9cfcd7b..1ea1dfafcf 100644 --- a/common/ASC.Core.Common/Context/SecurityContext.cs +++ b/common/ASC.Core.Common/Context/SecurityContext.cs @@ -84,7 +84,7 @@ public class SecurityContext } - public async Task AuthenticateMeAsync(string login, string passwordHash, Func> funcLoginEvent = null) + public async Task AuthenticateMeAsync(string login, string passwordHash, Func> funcLoginEvent = null, List additionalClaims = null) { ArgumentNullException.ThrowIfNull(login); ArgumentNullException.ThrowIfNull(passwordHash); @@ -92,7 +92,7 @@ public class SecurityContext var tenantid = await _tenantManager.GetCurrentTenantIdAsync(); var u = await _userManager.GetUsersByPasswordHashAsync(tenantid, login, passwordHash); - return await AuthenticateMeAsync(new UserAccount(u, tenantid, _userFormatter), funcLoginEvent); + return await AuthenticateMeAsync(new UserAccount(u, tenantid, _userFormatter), funcLoginEvent,additionalClaims); } public async Task AuthenticateMe(string cookie) @@ -168,7 +168,13 @@ public class SecurityContext return false; } - await AuthenticateMeWithoutCookieAsync(new UserAccount(new UserInfo { Id = userid }, tenant, _userFormatter)); + var claims = new List() + { + AuthConstants.Claim_ScopeRootWrite + }; + + await AuthenticateMeWithoutCookieAsync(new UserAccount(new UserInfo { Id = userid }, tenant, _userFormatter), claims); + return true; } catch (InvalidCredentialException ice) @@ -381,10 +387,13 @@ public class AuthContext internal ClaimsPrincipal Principal { - get => CustomSynchronizationContext.CurrentContext.CurrentPrincipal as ClaimsPrincipal ?? HttpContextAccessor?.HttpContext?.User; + get => CustomSynchronizationContext.CurrentContext?.CurrentPrincipal as ClaimsPrincipal ?? HttpContextAccessor?.HttpContext?.User; set { - CustomSynchronizationContext.CurrentContext.CurrentPrincipal = value; + if (CustomSynchronizationContext.CurrentContext != null) + { + CustomSynchronizationContext.CurrentContext.CurrentPrincipal = value; + } if (HttpContextAccessor?.HttpContext != null) { diff --git a/common/ASC.Core.Common/Security/Authorizing/AzManager.cs b/common/ASC.Core.Common/Security/Authorizing/AzManager.cs index 973f133c11..3d95c55210 100644 --- a/common/ASC.Core.Common/Security/Authorizing/AzManager.cs +++ b/common/ASC.Core.Common/Security/Authorizing/AzManager.cs @@ -56,7 +56,7 @@ public class AzManager internal async Task GetAzManagerAclAsync(ISubject subject, IAction action, ISecurityObjectId objectId, ISecurityObjectProvider securityObjProvider) { - if (action.AdministratorAlwaysAllow && (Constants.DocSpaceAdmin.ID == subject.ID || await _roleProvider.IsSubjectInRoleAsync(subject, Constants.DocSpaceAdmin) + if (action.AdministratorAlwaysAllow && (AuthConstants.DocSpaceAdmin.ID == subject.ID || await _roleProvider.IsSubjectInRoleAsync(subject, AuthConstants.DocSpaceAdmin) || (objectId is SecurityObject obj && await obj.IsMatchDefaultRulesAsync(subject, action, _roleProvider)))) { return AzManagerAcl.Allow; diff --git a/common/ASC.Core.Common/Security/Authorizing/RoleProvider.cs b/common/ASC.Core.Common/Security/Authorizing/RoleProvider.cs index 31f6ba2fe0..ead75ebbea 100644 --- a/common/ASC.Core.Common/Security/Authorizing/RoleProvider.cs +++ b/common/ASC.Core.Common/Security/Authorizing/RoleProvider.cs @@ -24,7 +24,7 @@ // 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 Constants = ASC.Common.Security.Authorizing.Constants; +using AuthConstants = ASC.Common.Security.Authorizing.AuthConstants; namespace ASC.Core.Security.Authorizing; @@ -53,9 +53,9 @@ class RoleProvider : IRoleProvider } } - if (roles.Any(r => r.ID == Constants.Collaborator.ID || r.ID == Constants.User.ID)) + if (roles.Any(r => r.ID == AuthConstants.Collaborator.ID || r.ID == AuthConstants.User.ID)) { - roles = roles.Where(r => r.ID != Constants.RoomAdmin.ID).ToList(); + roles = roles.Where(r => r.ID != AuthConstants.RoomAdmin.ID).ToList(); } return roles; diff --git a/common/ASC.Core.Common/Security/Security.cs b/common/ASC.Core.Common/Security/Security.cs index 342ae4d753..9ebd8ec6b1 100644 --- a/common/ASC.Core.Common/Security/Security.cs +++ b/common/ASC.Core.Common/Security/Security.cs @@ -25,7 +25,7 @@ // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode using UserConstants = ASC.Core.Users.Constants; -using Constants = ASC.Common.Security.Authorizing.Constants; +using AuthConstants = ASC.Common.Security.Authorizing.AuthConstants; namespace ASC.Core.Common.Security; @@ -34,26 +34,26 @@ public static class Security public static readonly Dictionary>> Rules = new() { { - Constants.RoomAdmin.ID, new Dictionary> + AuthConstants.RoomAdmin.ID, new Dictionary> { { - Constants.User.ID, new HashSet + AuthConstants.User.ID, new HashSet { - new(UserConstants.Action_EditGroups.ID, Constants.User), + new(UserConstants.Action_EditGroups.ID, AuthConstants.User), new(UserConstants.Action_AddRemoveUser.ID), } }, { - Constants.RoomAdmin.ID, new HashSet + AuthConstants.RoomAdmin.ID, new HashSet { - new(UserConstants.Action_EditGroups.ID, Constants.User), + new(UserConstants.Action_EditGroups.ID, AuthConstants.User), new(UserConstants.Action_AddRemoveUser.ID), } }, { - Constants.Collaborator.ID, new HashSet + AuthConstants.Collaborator.ID, new HashSet { - new(UserConstants.Action_EditGroups.ID, Constants.Collaborator), + new(UserConstants.Action_EditGroups.ID, AuthConstants.Collaborator), new(UserConstants.Action_AddRemoveUser.ID), } } diff --git a/common/ASC.Core.Common/Security/UserGroupObject.cs b/common/ASC.Core.Common/Security/UserGroupObject.cs index 18b314194c..c259959ddc 100644 --- a/common/ASC.Core.Common/Security/UserGroupObject.cs +++ b/common/ASC.Core.Common/Security/UserGroupObject.cs @@ -24,7 +24,7 @@ // 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 AuthConstants = ASC.Common.Security.Authorizing.Constants; +using AuthConstants = ASC.Common.Security.Authorizing.AuthConstants; namespace ASC.Core.Common.Security; diff --git a/common/ASC.Core.Common/Security/UserSecurityProvider.cs b/common/ASC.Core.Common/Security/UserSecurityProvider.cs index 0821ca7e31..e342e92737 100644 --- a/common/ASC.Core.Common/Security/UserSecurityProvider.cs +++ b/common/ASC.Core.Common/Security/UserSecurityProvider.cs @@ -24,7 +24,7 @@ // 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 AuthConstants = ASC.Common.Security.Authorizing.Constants; +using AuthConstants = ASC.Common.Security.Authorizing.AuthConstants; namespace ASC.Core.Users; diff --git a/common/ASC.Core.Common/Users/Constants.cs b/common/ASC.Core.Common/Users/Constants.cs index cd2f126a91..28436d1a78 100644 --- a/common/ASC.Core.Common/Users/Constants.cs +++ b/common/ASC.Core.Common/Users/Constants.cs @@ -25,7 +25,6 @@ // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode using Action = ASC.Common.Security.Authorizing.Action; -using AuthConst = ASC.Common.Security.Authorizing.Constants; namespace ASC.Core.Users; @@ -65,32 +64,32 @@ public sealed class Constants public static readonly GroupInfo GroupEveryone = new GroupInfo(SysGroupCategoryId) { - ID = AuthConst.Everyone.ID, - Name = AuthConst.Everyone.Name, + ID = AuthConstants.Everyone.ID, + Name = AuthConstants.Everyone.Name, }; public static readonly GroupInfo GroupUser = new GroupInfo(SysGroupCategoryId) { - ID = AuthConst.User.ID, - Name = AuthConst.User.Name, + ID = AuthConstants.User.ID, + Name = AuthConstants.User.Name, }; public static readonly GroupInfo GroupManager = new GroupInfo(SysGroupCategoryId) { - ID = AuthConst.RoomAdmin.ID, - Name = AuthConst.RoomAdmin.Name, + ID = AuthConstants.RoomAdmin.ID, + Name = AuthConstants.RoomAdmin.Name, }; public static readonly GroupInfo GroupAdmin = new GroupInfo(SysGroupCategoryId) { - ID = AuthConst.DocSpaceAdmin.ID, - Name = AuthConst.DocSpaceAdmin.Name, + ID = AuthConstants.DocSpaceAdmin.ID, + Name = AuthConstants.DocSpaceAdmin.Name, }; public static readonly GroupInfo GroupCollaborator = new(SysGroupCategoryId) { - ID = AuthConst.Collaborator.ID, - Name = AuthConst.Collaborator.Name, + ID = AuthConstants.Collaborator.ID, + Name = AuthConstants.Collaborator.Name, }; public static readonly GroupInfo[] BuildinGroups = new[] diff --git a/common/ASC.FederatedLogin/ASC.FederatedLogin.csproj b/common/ASC.FederatedLogin/ASC.FederatedLogin.csproj index 7ddabedbaa..b69553d5f9 100644 --- a/common/ASC.FederatedLogin/ASC.FederatedLogin.csproj +++ b/common/ASC.FederatedLogin/ASC.FederatedLogin.csproj @@ -31,7 +31,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/config/nlog.config b/config/nlog.config index 59fec170c0..8d9a3fd498 100644 --- a/config/nlog.config +++ b/config/nlog.config @@ -44,6 +44,8 @@ - + + + \ No newline at end of file diff --git a/products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs b/products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs index 773d9d3991..c092387861 100644 --- a/products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs +++ b/products/ASC.Files/Core/HttpHandlers/FileHandler.ashx.cs @@ -665,7 +665,7 @@ public class FileHandlerService #pragma warning disable CS0618 // Type or member is obsolete var stringPayload = JwtBuilder.Create() .WithAlgorithm(new HMACSHA256Algorithm()) - .WithSerializer(new JwtSerializer()) + .WithJsonSerializer(new JwtSerializer()) .WithSecret(_fileUtility.SignatureSecret) .MustVerifySignature() .Decode(header); @@ -779,8 +779,8 @@ public class FileHandlerService #pragma warning disable CS0618 // Type or member is obsolete var stringPayload = JwtBuilder.Create() .WithAlgorithm(new HMACSHA256Algorithm()) - .WithSerializer(new JwtSerializer()) .WithSecret(_fileUtility.SignatureSecret) + .WithJsonSerializer(new JwtSerializer()) .MustVerifySignature() .Decode(header); #pragma warning restore CS0618 // Type or member is obsolete @@ -1516,7 +1516,7 @@ public class FileHandlerService #pragma warning disable CS0618 // Type or member is obsolete var dataString = JwtBuilder.Create() .WithAlgorithm(new HMACSHA256Algorithm()) - .WithSerializer(new JwtSerializer()) + .WithJsonSerializer(new JwtSerializer()) .WithSecret(_fileUtility.SignatureSecret) .MustVerifySignature() .Decode(fileData.Token); @@ -1551,7 +1551,7 @@ public class FileHandlerService #pragma warning disable CS0618 // Type or member is obsolete var stringPayload = JwtBuilder.Create() .WithAlgorithm(new HMACSHA256Algorithm()) - .WithSerializer(new JwtSerializer()) + .WithJsonSerializer(new JwtSerializer()) .WithSecret(_fileUtility.SignatureSecret) .MustVerifySignature() .Decode(header); diff --git a/products/ASC.People/Server/Api/PeopleControllerBase.cs b/products/ASC.People/Server/Api/PeopleControllerBase.cs index 3bdcd06a7d..b335ea2da4 100644 --- a/products/ASC.People/Server/Api/PeopleControllerBase.cs +++ b/products/ASC.People/Server/Api/PeopleControllerBase.cs @@ -49,7 +49,8 @@ public abstract class PeopleControllerBase : ApiControllerBase _userPhotoManager = userPhotoManager; _httpClientFactory = httpClientFactory; _httpContextAccessor = httpContextAccessor; - } + } + protected async Task GetUserInfoAsync(string userNameOrId) { diff --git a/products/ASC.People/Server/Api/UserController.cs b/products/ASC.People/Server/Api/UserController.cs index 3d7fcc7a66..6bbb2d5668 100644 --- a/products/ASC.People/Server/Api/UserController.cs +++ b/products/ASC.People/Server/Api/UserController.cs @@ -144,6 +144,19 @@ public class UserController : PeopleControllerBase _usersQuotaSyncOperation = usersQuotaSyncOperation; } + [HttpGet("tokendiagnostics")] + public object GetClaims() + { + var result = new + { + Name = User.Identity?.Name ?? "Unknown Name", + Claims = (from c in User.Claims select c.Type + ":" + c.Value).ToList() + }; + + return result; + } + + [HttpPost("active")] public async Task AddMemberAsActivatedAsync(MemberRequestDto inDto) { diff --git a/web/ASC.Web.Studio/Startup.cs b/web/ASC.Web.Studio/Startup.cs index 19f1590396..ebcdc14aca 100644 --- a/web/ASC.Web.Studio/Startup.cs +++ b/web/ASC.Web.Studio/Startup.cs @@ -36,9 +36,7 @@ public class Startup : BaseStartup base.Configure(app, env); app.UseRouting(); - - app.UseAuthentication(); - + app.UseEndpoints(endpoints => { endpoints.InitializeHttpHandlers();