added a secure link to invite to the portal

This commit is contained in:
Maksim Chegulov 2022-05-13 19:25:28 +03:00
parent 5d2946b47f
commit 025d13557d
9 changed files with 331 additions and 194 deletions

View File

@ -46,7 +46,8 @@ global using ASC.Api.Core.Convention;
global using ASC.Api.Core.Core;
global using ASC.Api.Core.Extensions;
global using ASC.Api.Core.Middleware;
global using ASC.Api.Core.Routing;
global using ASC.Api.Core.Routing;
global using ASC.Api.Core.Security;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.DependencyInjection;
@ -66,7 +67,10 @@ global using ASC.Core.Users;
global using ASC.EventBus;
global using ASC.EventBus.Abstractions;
global using ASC.EventBus.RabbitMQ;
global using ASC.IPSecurity;
global using ASC.IPSecurity;
global using ASC.MessagingSystem.Data;
global using ASC.MessagingSystem.Core;
global using ASC.MessagingSystem.Models;
global using ASC.Security.Cryptography;
global using ASC.Web.Api.Routing;
global using ASC.Web.Core;
@ -99,7 +103,8 @@ global using Microsoft.AspNetCore.Mvc.Routing;
global using Microsoft.AspNetCore.Routing;
global using Microsoft.AspNetCore.Routing.Constraints;
global using Microsoft.AspNetCore.Routing.Patterns;
global using Microsoft.AspNetCore.Server.Kestrel.Core;
global using Microsoft.AspNetCore.Server.Kestrel.Core;
global using Microsoft.AspNetCore.WebUtilities;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Diagnostics.HealthChecks;
@ -115,7 +120,9 @@ global using NLog;
global using NLog.Config;
global using NLog.Extensions.Logging;
global using RabbitMQ.Client;
global using RabbitMQ.Client;
global using AutoMapper;
global using StackExchange.Redis.Extensions.Core.Configuration;
global using StackExchange.Redis.Extensions.Newtonsoft;

View File

@ -0,0 +1,165 @@
// (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 static ASC.Security.Cryptography.EmailValidationKeyProvider;
namespace ASC.Api.Core.Security;
[Transient]
public class EmailValidationKeyModelHelper
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly EmailValidationKeyProvider _provider;
private readonly AuthContext _authContext;
private readonly UserManager _userManager;
private readonly AuthManager _authentication;
private readonly RoomLinksService _roomLinksService;
public EmailValidationKeyModelHelper(
IHttpContextAccessor httpContextAccessor,
EmailValidationKeyProvider provider,
AuthContext authContext,
UserManager userManager,
AuthManager authentication,
RoomLinksService roomLinksService)
{
_httpContextAccessor = httpContextAccessor;
_provider = provider;
_authContext = authContext;
_userManager = userManager;
_authentication = authentication;
_roomLinksService = roomLinksService;
}
public EmailValidationKeyModel GetModel()
{
var request = QueryHelpers.ParseQuery(_httpContextAccessor.HttpContext.Request.Headers["confirm"]);
request.TryGetValue("type", out var type);
ConfirmType? cType = null;
if (Enum.TryParse<ConfirmType>(type, out var confirmType))
{
cType = confirmType;
}
request.TryGetValue("key", out var key);
request.TryGetValue("emplType", out var emplType);
Enum.TryParse<EmployeeType>(emplType, out var employeeType);
request.TryGetValue("email", out var _email);
request.TryGetValue("uid", out var userIdKey);
Guid.TryParse(userIdKey, out var userId);
return new EmailValidationKeyModel
{
Email = _email,
EmplType = employeeType,
Key = key,
Type = cType,
UiD = userId
};
}
public ValidationResult Validate(EmailValidationKeyModel inDto)
{
var (key, emplType, email, uiD, type, fileShare, roomId) = inDto;
ValidationResult checkKeyResult;
switch (type)
{
case ConfirmType.EmpInvite:
checkKeyResult = _provider.ValidateEmailKey(email + type + (int)emplType, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.LinkInvite:
checkKeyResult = _provider.ValidateEmailKey(type.ToString() + (int)emplType, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.RoomInvite:
checkKeyResult = _provider.ValidateEmailKey(email + type + ((int)emplType + (int)fileShare + roomId), key, _provider.ValidAuthKeyInterval);
if (checkKeyResult == ValidationResult.Ok &&
!_roomLinksService.VisitProcess(roomId, email, key, _provider.ValidVisitLinkInterval))
{
checkKeyResult = ValidationResult.Expired;
}
break;
case ConfirmType.PortalOwnerChange:
checkKeyResult = _provider.ValidateEmailKey(email + type + uiD.HasValue, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.EmailChange:
checkKeyResult = _provider.ValidateEmailKey(email + type + _authContext.CurrentAccount.ID, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.PasswordChange:
var hash = _authentication.GetUserPasswordStamp(_userManager.GetUserByEmail(email).Id).ToString("s");
checkKeyResult = _provider.ValidateEmailKey(email + type + hash, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.Activation:
checkKeyResult = _provider.ValidateEmailKey(email + type + uiD, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.ProfileRemove:
// validate UiD
var user = _userManager.GetUsers(uiD.GetValueOrDefault());
if (user == null || user.Status == EmployeeStatus.Terminated || _authContext.IsAuthenticated && _authContext.CurrentAccount.ID != uiD)
{
return ValidationResult.Invalid;
}
checkKeyResult = _provider.ValidateEmailKey(email + type + uiD, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.Wizard:
checkKeyResult = _provider.ValidateEmailKey("" + type, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.PhoneActivation:
case ConfirmType.PhoneAuth:
case ConfirmType.TfaActivation:
case ConfirmType.TfaAuth:
case ConfirmType.Auth:
checkKeyResult = _provider.ValidateEmailKey(email + type, key, _provider.ValidAuthKeyInterval);
break;
case ConfirmType.PortalContinue:
checkKeyResult = _provider.ValidateEmailKey(email + type, key);
break;
default:
checkKeyResult = _provider.ValidateEmailKey(email + type, key, _provider.ValidEmailKeyInterval);
break;
}
return checkKeyResult;
}
}

View File

@ -0,0 +1,135 @@
// (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 ASC.MessagingSystem.Models;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace ASC.Api.Core.Security;
[Scope]
public class RoomLinksService
{
private readonly CommonLinkUtility _commonLinkUtility;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly MessageTarget _messageTarget;
private readonly UserManager _userManager;
private readonly MessageFactory _messageFactory;
private readonly Lazy<MessagesContext> _messagesContext;
private readonly IMapper _mapper;
public RoomLinksService(
CommonLinkUtility commonLinkUtility,
IHttpContextAccessor httpContextAccessor,
MessageTarget messageTarget,
UserManager userManager,
MessageFactory messageFactory,
DbContextManager<MessagesContext> dbContextManager,
IMapper mapper)
{
_commonLinkUtility = commonLinkUtility;
_httpContextAccessor = httpContextAccessor;
_messageTarget = messageTarget;
_userManager = userManager;
_messageFactory = messageFactory;
_messagesContext = new Lazy<MessagesContext>(() => dbContextManager.Value);
_mapper = mapper;
}
public string GenerateLink<T>(T id, string email, int fileShare, EmployeeType employeeType, Guid guid)
{
var user = _userManager.GetUserByEmail(email);
if (user != ASC.Core.Users.Constants.LostUser)
{
throw new Exception();
}
var postifx = ((int)employeeType + fileShare) + id.ToString();
var link = _commonLinkUtility.GetConfirmationUrl(email, ConfirmType.RoomInvite, postifx, guid)
+ $"&emplType={employeeType:d}&roomId={id}";
return link;
}
public bool VisitProcess(string id, string email, string key, TimeSpan interval)
{
var user = _userManager.GetUserByEmail(email);
if (user != ASC.Core.Users.Constants.LostUser)
{
return false;
}
var message = GetLinkInfo(id, email, key);
if (message == null)
{
SaveVisitLinkInfo(id, email, key, interval);
return true;
}
return message.Date <= DateTime.Now ? false : true;
}
private void SaveVisitLinkInfo(string id, string email, string key, TimeSpan interval)
{
var headers = _httpContextAccessor?.HttpContext?.Request?.Headers;
var target = _messageTarget.Create(new[] {id, email});
var message = _messageFactory.Create(null, headers, MessageAction.RoomInviteLinkUsed, target);
var audit = _mapper.Map<EventMessage, AuditEvent>(message);
audit.Date = DateTime.Now + interval;
audit.DescriptionRaw = Serialize(new[] { key });
var context = _messagesContext.Value;
context.AuditEvents.Add(audit);
context.SaveChanges();
}
private AuditEvent GetLinkInfo(string id, string email, string key)
{
var context = _messagesContext.Value;
var target = _messageTarget.Create(new[] { id, email });
var message = context.AuditEvents.Where(a => a.Target == target.ToString() &&
a.DescriptionRaw == Serialize(new[] { key })).FirstOrDefault();
return message;
}
private string Serialize(IEnumerable<string> description)
{
return JsonConvert.SerializeObject(description,
new JsonSerializerSettings
{
DateTimeZoneHandling = DateTimeZoneHandling.Utc
});
}
}

View File

@ -35,11 +35,13 @@ public class EmailValidationKeyProvider
Invalid,
Expired
}
public TimeSpan ValidEmailKeyInterval { get; }
public TimeSpan ValidAuthKeyInterval { get; }
public TimeSpan ValidVisitLinkInterval { get; }
private readonly ILog _logger;
private static readonly DateTime _from = new DateTime(2010, 01, 01, 0, 0, 0, DateTimeKind.Utc);
internal readonly TimeSpan ValidEmailKeyInterval;
internal readonly TimeSpan ValidAuthKeyInterval;
private readonly MachinePseudoKeys _machinePseudoKeys;
private readonly TenantManager _tenantManager;
@ -55,7 +57,8 @@ public class EmailValidationKeyProvider
{
authValidInterval = TimeSpan.FromHours(1);
}
ValidVisitLinkInterval = TimeSpan.FromMinutes(1);
ValidEmailKeyInterval = validInterval;
ValidAuthKeyInterval = authValidInterval;
_logger = logger;
@ -162,139 +165,11 @@ public class EmailValidationKeyModel
public string Email { get; set; }
public Guid? UiD { get; set; }
public ConfirmType? Type { get; set; }
public FileShare? FileShare { get; set; }
public int? FileShare { get; set; }
public string RoomId { get; set; }
public void Deconstruct(out string key, out EmployeeType? emplType, out string email, out Guid? uiD, out ConfirmType? type, out FileShare? fileShare, out string roomId)
public void Deconstruct(out string key, out EmployeeType? emplType, out string email, out Guid? uiD, out ConfirmType? type, out int? fileShare, out string roomId)
{
(key, emplType, email, uiD, type, fileShare, roomId) = (Key, EmplType, Email, UiD, Type, FileShare, RoomId);
}
}
[Transient]
public class EmailValidationKeyModelHelper
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly EmailValidationKeyProvider _provider;
private readonly AuthContext _authContext;
private readonly UserManager _userManager;
private readonly AuthManager _authentication;
public EmailValidationKeyModelHelper(
IHttpContextAccessor httpContextAccessor,
EmailValidationKeyProvider provider,
AuthContext authContext,
UserManager userManager,
AuthManager authentication)
{
_httpContextAccessor = httpContextAccessor;
_provider = provider;
_authContext = authContext;
_userManager = userManager;
_authentication = authentication;
}
public EmailValidationKeyModel GetModel()
{
var request = QueryHelpers.ParseQuery(_httpContextAccessor.HttpContext.Request.Headers["confirm"]);
request.TryGetValue("type", out var type);
ConfirmType? cType = null;
if (Enum.TryParse<ConfirmType>(type, out var confirmType))
{
cType = confirmType;
}
request.TryGetValue("key", out var key);
request.TryGetValue("emplType", out var emplType);
Enum.TryParse<EmployeeType>(emplType, out var employeeType);
request.TryGetValue("email", out var _email);
request.TryGetValue("uid", out var userIdKey);
Guid.TryParse(userIdKey, out var userId);
return new EmailValidationKeyModel
{
Email = _email,
EmplType = employeeType,
Key = key,
Type = cType,
UiD = userId
};
}
public ValidationResult Validate(EmailValidationKeyModel inDto)
{
var (key, emplType, email, uiD, type, fileShare, roomId) = inDto;
ValidationResult checkKeyResult;
switch (type)
{
case ConfirmType.EmpInvite:
checkKeyResult = _provider.ValidateEmailKey(email + type + (int)emplType, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.LinkInvite:
checkKeyResult = _provider.ValidateEmailKey(type.ToString() + (int)emplType, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.RoomInvite:
checkKeyResult = _provider.ValidateEmailKey(email + type + ((int)emplType + (int)fileShare + roomId), key, _provider.ValidAuthKeyInterval);
break;
case ConfirmType.PortalOwnerChange:
checkKeyResult = _provider.ValidateEmailKey(email + type + uiD.HasValue, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.EmailChange:
checkKeyResult = _provider.ValidateEmailKey(email + type + _authContext.CurrentAccount.ID, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.PasswordChange:
var hash = _authentication.GetUserPasswordStamp(_userManager.GetUserByEmail(email).Id).ToString("s");
checkKeyResult = _provider.ValidateEmailKey(email + type + hash, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.Activation:
checkKeyResult = _provider.ValidateEmailKey(email + type + uiD, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.ProfileRemove:
// validate UiD
var user = _userManager.GetUsers(uiD.GetValueOrDefault());
if (user == null || user.Status == EmployeeStatus.Terminated || _authContext.IsAuthenticated && _authContext.CurrentAccount.ID != uiD)
{
return ValidationResult.Invalid;
}
checkKeyResult = _provider.ValidateEmailKey(email + type + uiD, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.Wizard:
checkKeyResult = _provider.ValidateEmailKey("" + type, key, _provider.ValidEmailKeyInterval);
break;
case ConfirmType.PhoneActivation:
case ConfirmType.PhoneAuth:
case ConfirmType.TfaActivation:
case ConfirmType.TfaAuth:
case ConfirmType.Auth:
checkKeyResult = _provider.ValidateEmailKey(email + type, key, _provider.ValidAuthKeyInterval);
break;
case ConfirmType.PortalContinue:
checkKeyResult = _provider.ValidateEmailKey(email + type, key);
break;
default:
checkKeyResult = _provider.ValidateEmailKey(email + type, key, _provider.ValidEmailKeyInterval);
break;
}
return checkKeyResult;
}
}
}

View File

@ -525,6 +525,7 @@ public enum MessageAction
#region others
ContactAdminMailSent = 7000,
RoomInviteLinkUsed = 7001,
#endregion

View File

@ -1,47 +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.Files.Core.VirtualRooms;
[Scope]
public class RoomLinksManager
{
private readonly CommonLinkUtility _commonLinkUtility;
public RoomLinksManager(CommonLinkUtility commonLinkUtility)
{
_commonLinkUtility = commonLinkUtility;
}
public string GenerateLink<T>(T id, string email, FileShare share, EmployeeType employeeType, Guid guid)
{
var postifx = ((int)employeeType + (int)share) + id.ToString();
var link = _commonLinkUtility.GetConfirmationUrl(email, ConfirmType.RoomInvite, postifx, guid);
return $"{link}&emplType={employeeType:d}&roomId={id}";
}
}

View File

@ -24,14 +24,12 @@
// 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.Web.Core;
namespace ASC.Files.Api;
[ConstraintRoute("int")]
public class VirtualRoomsControllerInternal : VirtualRoomsController<int>
{
public VirtualRoomsControllerInternal(FoldersControllerHelper<int> foldersControllerHelper, GlobalFolderHelper globalFolderHelper, FileStorageService<int> fileStorageService, FolderDtoHelper folderDtoHelper, FileOperationDtoHelper fileOperationDtoHelper, SecurityControllerHelper<int> securityControllerHelper, CoreBaseSettings coreBaseSettings, FolderContentDtoHelper folderContentDtoHelper, ApiContext apiContext, WebItemSecurity webItemSecurity, AuthContext authContext, RoomLinksManager roomLinksManager) : base(foldersControllerHelper, globalFolderHelper, fileStorageService, folderDtoHelper, fileOperationDtoHelper, securityControllerHelper, coreBaseSettings, folderContentDtoHelper, apiContext, webItemSecurity, authContext, roomLinksManager)
public VirtualRoomsControllerInternal(FoldersControllerHelper<int> foldersControllerHelper, GlobalFolderHelper globalFolderHelper, FileStorageService<int> fileStorageService, FolderDtoHelper folderDtoHelper, FileOperationDtoHelper fileOperationDtoHelper, SecurityControllerHelper<int> securityControllerHelper, CoreBaseSettings coreBaseSettings, FolderContentDtoHelper folderContentDtoHelper, ApiContext apiContext, WebItemSecurity webItemSecurity, AuthContext authContext, RoomLinksService roomLinksManager) : base(foldersControllerHelper, globalFolderHelper, fileStorageService, folderDtoHelper, fileOperationDtoHelper, securityControllerHelper, coreBaseSettings, folderContentDtoHelper, apiContext, webItemSecurity, authContext, roomLinksManager)
{
}
}
@ -49,13 +47,13 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
private readonly ApiContext _apiContext;
private readonly WebItemSecurity _webItemSecurity;
private readonly AuthContext _authContext;
private readonly RoomLinksManager _roomLinksManager;
private readonly RoomLinksService _roomLinksManager;
public VirtualRoomsController(FoldersControllerHelper<T> foldersControllerHelper, GlobalFolderHelper globalFolderHelper,
FileStorageService<T> fileStorageService, FolderDtoHelper folderDtoHelper, FileOperationDtoHelper fileOperationDtoHelper,
SecurityControllerHelper<T> securityControllerHelper, CoreBaseSettings coreBaseSettings,
FolderContentDtoHelper folderContentDtoHelper, ApiContext apiContext, WebItemSecurity webItemSecurity, AuthContext authContext,
RoomLinksManager roomLinksManager)
RoomLinksService roomLinksManager)
{
_foldersControllerHelper = foldersControllerHelper;
_globalFolderHelper = globalFolderHelper;
@ -185,7 +183,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
{
ErrorIfNotDocSpace();
return _roomLinksManager.GenerateLink(id, inDto.Email, inDto.Access, inDto.EmployeeType, _authContext.CurrentAccount.ID);
return _roomLinksManager.GenerateLink(id, inDto.Email, (int)inDto.Access, inDto.EmployeeType, _authContext.CurrentAccount.ID);
}
private void ErrorIfNotDocSpace()

View File

@ -33,7 +33,8 @@ global using System.Web;
global using ASC.Api.Core;
global using ASC.Api.Core.Convention;
global using ASC.Api.Core.Extensions;
global using ASC.Api.Core.Extensions;
global using ASC.Api.Core.Security;
global using ASC.Api.Core.Routing;
global using ASC.Api.Utils;
global using ASC.Common;
@ -59,7 +60,8 @@ global using ASC.Web.Api.Routing;
global using ASC.Web.Core.Files;
global using ASC.Web.Core.PublicResources;
global using ASC.Web.Core.Users;
global using ASC.Web.Files;
global using ASC.Web.Files;
global using ASC.Web.Core;
global using ASC.Web.Files.Classes;
global using ASC.Web.Files.Configuration;
global using ASC.Web.Files.Core.Compress;

View File

@ -39,7 +39,8 @@ global using System.Web;
global using ASC.Api.Collections;
global using ASC.Api.Core;
global using ASC.Api.Core.Convention;
global using ASC.Api.Core.Extensions;
global using ASC.Api.Core.Extensions;
global using ASC.Api.Core.Security;
global using ASC.Api.Settings;
global using ASC.Api.Utils;
global using ASC.AuditTrail;