From c73e4f7a1df8ff04b2699f1bb991c6620796af23 Mon Sep 17 00:00:00 2001 From: Alexey Bannov Date: Thu, 7 Sep 2023 09:01:43 +0300 Subject: [PATCH 1/2] rate limiter: max 10000 requests per user --- common/ASC.Api.Core/Core/BaseStartup.cs | 46 ++++++++++++++----- .../ASC.People/Server/Api/UserController.cs | 1 + 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/common/ASC.Api.Core/Core/BaseStartup.cs b/common/ASC.Api.Core/Core/BaseStartup.cs index c2e6b108f6..7b93e3bc80 100644 --- a/common/ASC.Api.Core/Core/BaseStartup.cs +++ b/common/ASC.Api.Core/Core/BaseStartup.cs @@ -24,6 +24,8 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +using Flurl.Util; + using JsonConverter = System.Text.Json.Serialization.JsonConverter; namespace ASC.Api.Core; @@ -112,7 +114,7 @@ public abstract class BaseStartup if (userId == null) { - return RateLimitPartition.GetNoLimiter("no_limiter"); + userId = httpContext?.Connection.RemoteIpAddress.ToInvariantString(); } var permitLimit = 1500; @@ -122,7 +124,7 @@ public abstract class BaseStartup partitionKey = $"sw_{userId}"; return RedisRateLimitPartition.GetSlidingWindowRateLimiter(partitionKey, key => new RedisSlidingWindowRateLimiterOptions - { + { PermitLimit = permitLimit, Window = TimeSpan.FromMinutes(1), ConnectionMultiplexerFactory = () => connectionMultiplexer @@ -136,7 +138,7 @@ public abstract class BaseStartup if (userId == null) { - return RateLimitPartition.GetNoLimiter("no_limiter"); + userId = httpContext?.Connection.RemoteIpAddress.ToInvariantString(); } if (String.Compare(httpContext.Request.Method, "GET", true) == 0) @@ -157,17 +159,37 @@ public abstract class BaseStartup QueueLimit = 0, ConnectionMultiplexerFactory = () => connectionMultiplexer }); - } + }), + PartitionedRateLimiter.Create(httpContext => + { + var userId = httpContext?.User?.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value; + + if (userId == null) + { + userId = httpContext?.Connection.RemoteIpAddress.ToInvariantString(); + } + + var partitionKey = $"fw_post_put_{userId}"; + var permitLimit = 10000; + + if (!(String.Compare(httpContext.Request.Method, "POST", true) == 0 || + String.Compare(httpContext.Request.Method, "PUT", true) == 0)) + { + return RateLimitPartition.GetNoLimiter("no_limiter"); + } + + return RedisRateLimitPartition.GetFixedWindowRateLimiter(partitionKey, key => new RedisFixedWindowRateLimiterOptions + { + PermitLimit = permitLimit, + Window = TimeSpan.FromDays(1), + ConnectionMultiplexerFactory = () => connectionMultiplexer + }); + } )); - options.AddPolicy("sensitive_api", httpContext => { + options.AddPolicy("sensitive_api", httpContext => + { var userId = httpContext?.User?.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value; - - if (userId == null) - { - return RateLimitPartition.GetNoLimiter("no_limiter"); - } - var permitLimit = 5; var partitionKey = $"sensitive_api_{userId}"; @@ -178,7 +200,7 @@ public abstract class BaseStartup ConnectionMultiplexerFactory = () => connectionMultiplexer }); }); - + options.OnRejected = (context, ct) => RateLimitMetadata.OnRejected(context.HttpContext, context.Lease, ct); }); diff --git a/products/ASC.People/Server/Api/UserController.cs b/products/ASC.People/Server/Api/UserController.cs index df732db748..d50d9c7bd7 100644 --- a/products/ASC.People/Server/Api/UserController.cs +++ b/products/ASC.People/Server/Api/UserController.cs @@ -388,6 +388,7 @@ public class UserController : PeopleControllerBase /// api/2.0/people/{userid}/password /// PUT [HttpPut("{userid}/password")] + [EnableRateLimiting("sensitive_api")] [Authorize(AuthenticationSchemes = "confirm", Roles = "PasswordChange,EmailChange,Activation,EmailActivation,Everyone")] public async Task ChangeUserPassword(Guid userid, MemberRequestDto inDto) { From e419f8ab403ece6896200a5c13db526676310692 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Tue, 12 Sep 2023 14:54:59 +0300 Subject: [PATCH 2/2] added default link shortening --- .../ASC.Files/Core/Core/FileStorageService.cs | 4 ++- products/ASC.Files/Core/Utils/FileSharing.cs | 31 ++++++++++--------- .../ASC.People/Server/Api/UserController.cs | 15 +++++---- products/ASC.People/Server/GlobalUsings.cs | 6 ++-- web/ASC.Web.Api/Api/PortalController.cs | 6 ++-- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/products/ASC.Files/Core/Core/FileStorageService.cs b/products/ASC.Files/Core/Core/FileStorageService.cs index 78f1a47865..40690e4889 100644 --- a/products/ASC.Files/Core/Core/FileStorageService.cs +++ b/products/ASC.Files/Core/Core/FileStorageService.cs @@ -3172,7 +3172,9 @@ public class FileStorageService //: IFileStorageService } var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, share.Access, _authContext.CurrentAccount.ID); - await _studioNotifyService.SendEmailRoomInviteAsync(user.Email, room.Title, link); + var shortenLink = await _urlShortener.GetShortenLinkAsync(link); + + await _studioNotifyService.SendEmailRoomInviteAsync(user.Email, room.Title, shortenLink); } } diff --git a/products/ASC.Files/Core/Utils/FileSharing.cs b/products/ASC.Files/Core/Utils/FileSharing.cs index 5f7334fe66..3abaf41d72 100644 --- a/products/ASC.Files/Core/Utils/FileSharing.cs +++ b/products/ASC.Files/Core/Utils/FileSharing.cs @@ -44,10 +44,9 @@ public class FileSharingAceHelper private readonly FilesSettingsHelper _filesSettingsHelper; private readonly InvitationLinkService _invitationLinkService; private readonly StudioNotifyService _studioNotifyService; - private readonly UsersInRoomChecker _usersInRoomChecker; private readonly UserManagerWrapper _userManagerWrapper; private readonly CountPaidUserChecker _countPaidUserChecker; - private readonly ILogger _logger; + private readonly IUrlShortener _urlShortener; public FileSharingAceHelper( FileSecurity fileSecurity, @@ -64,10 +63,9 @@ public class FileSharingAceHelper FilesSettingsHelper filesSettingsHelper, InvitationLinkService invitationLinkService, StudioNotifyService studioNotifyService, - ILoggerProvider loggerProvider, - UsersInRoomChecker usersInRoomChecker, UserManagerWrapper userManagerWrapper, - CountPaidUserChecker countPaidUserChecker) + CountPaidUserChecker countPaidUserChecker, + IUrlShortener urlShortener) { _fileSecurity = fileSecurity; _coreBaseSettings = coreBaseSettings; @@ -83,10 +81,9 @@ public class FileSharingAceHelper _filesSettingsHelper = filesSettingsHelper; _invitationLinkService = invitationLinkService; _studioNotifyService = studioNotifyService; - _usersInRoomChecker = usersInRoomChecker; - _logger = loggerProvider.CreateLogger("ASC.Files"); _userManagerWrapper = userManagerWrapper; _countPaidUserChecker = countPaidUserChecker; + _urlShortener = urlShortener; } public async Task<(bool, string)> SetAceObjectAsync(List aceWrappers, FileEntry entry, bool notify, string message, AceAdvancedSettingsWrapper advancedSettings) @@ -233,8 +230,9 @@ public class FileSharingAceHelper if (emailInvite) { var link = await _invitationLinkService.GetInvitationLinkAsync(w.Email, share, _authContext.CurrentAccount.ID); - await _studioNotifyService.SendEmailRoomInviteAsync(w.Email, entry.Title, link); - _logger.Debug(link); + var shortenLink = await _urlShortener.GetShortenLinkAsync(link); + + await _studioNotifyService.SendEmailRoomInviteAsync(w.Email, entry.Title, shortenLink); } if (w.Id == FileConstant.ShareLinkId) @@ -439,6 +437,7 @@ public class FileSharing private readonly FilesSettingsHelper _filesSettingsHelper; private readonly InvitationLinkService _invitationLinkService; private readonly ExternalShare _externalShare; + private readonly IUrlShortener _urlShortener; public FileSharing( Global global, @@ -452,7 +451,8 @@ public class FileSharing FileSharingHelper fileSharingHelper, FilesSettingsHelper filesSettingsHelper, InvitationLinkService invitationLinkService, - ExternalShare externalShare) + ExternalShare externalShare, + IUrlShortener urlShortener) { _global = global; _fileSecurity = fileSecurity; @@ -466,6 +466,7 @@ public class FileSharing _logger = logger; _invitationLinkService = invitationLinkService; _externalShare = externalShare; + _urlShortener = urlShortener; } public async Task CanSetAccessAsync(FileEntry entry) @@ -563,10 +564,12 @@ public class FileSharing { continue; } - - w.Link = r.SubjectType == SubjectType.InvitationLink ? - _invitationLinkService.GetInvitationLink(r.Subject, _authContext.CurrentAccount.ID) : - await _externalShare.GetLinkAsync(r.Subject); + + var link = r.SubjectType == SubjectType.InvitationLink + ? _invitationLinkService.GetInvitationLink(r.Subject, _authContext.CurrentAccount.ID) + : await _externalShare.GetLinkAsync(r.Subject); + + w.Link = await _urlShortener.GetShortenLinkAsync(link); w.SubjectGroup = true; w.CanEditAccess = false; w.FileShareOptions.Password = await _externalShare.GetPasswordAsync(w.FileShareOptions.Password); diff --git a/products/ASC.People/Server/Api/UserController.cs b/products/ASC.People/Server/Api/UserController.cs index c4ab56af86..9f389b7296 100644 --- a/products/ASC.People/Server/Api/UserController.cs +++ b/products/ASC.People/Server/Api/UserController.cs @@ -24,8 +24,6 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode -using Microsoft.AspNetCore.RateLimiting; - namespace ASC.People.Api; public class UserController : PeopleControllerBase @@ -65,6 +63,7 @@ public class UserController : PeopleControllerBase private readonly UsersQuotaSyncOperation _usersQuotaSyncOperation; private readonly CountUserChecker _countUserChecker; private readonly UsersInRoomChecker _usersInRoomChecker; + private readonly IUrlShortener _urlShortener; public UserController( ICache cache, @@ -105,7 +104,8 @@ public class UserController : PeopleControllerBase CountPaidUserChecker countPaidUserChecker, CountUserChecker activeUsersChecker, UsersInRoomChecker usersInRoomChecker, - IQuotaService quotaService) + IQuotaService quotaService, + IUrlShortener urlShortener) : base(userManager, permissionContext, apiContext, userPhotoManager, httpClientFactory, httpContextAccessor) { _cache = cache; @@ -141,6 +141,7 @@ public class UserController : PeopleControllerBase _usersInRoomChecker = usersInRoomChecker; _quotaService = quotaService; _usersQuotaSyncOperation = usersQuotaSyncOperation; + _urlShortener = urlShortener; } /// @@ -363,9 +364,9 @@ public class UserController : PeopleControllerBase var user = await _userManagerWrapper.AddInvitedUserAsync(invite.Email, invite.Type); var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, invite.Type, _authContext.CurrentAccount.ID); + var shortenLink = await _urlShortener.GetShortenLinkAsync(link); - await _studioNotifyService.SendDocSpaceInviteAsync(user.Email, link); - _logger.Debug(link); + await _studioNotifyService.SendDocSpaceInviteAsync(user.Email, shortenLink); } var result = new List(); @@ -1054,7 +1055,9 @@ public class UserController : PeopleControllerBase } var link = await _invitationLinkService.GetInvitationLinkAsync(user.Email, type, _authContext.CurrentAccount.ID); - await _studioNotifyService.SendDocSpaceInviteAsync(user.Email, link); + var shortenLink = await _urlShortener.GetShortenLinkAsync(link); + + await _studioNotifyService.SendDocSpaceInviteAsync(user.Email, shortenLink); } else { diff --git a/products/ASC.People/Server/GlobalUsings.cs b/products/ASC.People/Server/GlobalUsings.cs index 9a69d9a3cc..8654c02179 100644 --- a/products/ASC.People/Server/GlobalUsings.cs +++ b/products/ASC.People/Server/GlobalUsings.cs @@ -74,7 +74,8 @@ global using ASC.Web.Core; global using ASC.Web.Core.Mobile; global using ASC.Web.Core.PublicResources; global using ASC.Web.Core.Quota; -global using ASC.Web.Core.Users; +global using ASC.Web.Core.Users; +global using ASC.Web.Core.Utility; global using ASC.Web.Files; global using ASC.Web.Studio.Core; global using ASC.Web.Studio.Core.Notify; @@ -83,7 +84,8 @@ global using ASC.Web.Studio.Utility; global using Autofac; global using Microsoft.AspNetCore.Http.Extensions; -global using Microsoft.AspNetCore.Mvc; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.AspNetCore.RateLimiting; global using Microsoft.EntityFrameworkCore; global using Microsoft.Extensions.Hosting.WindowsServices; diff --git a/web/ASC.Web.Api/Api/PortalController.cs b/web/ASC.Web.Api/Api/PortalController.cs index 366eaa753a..f794ca093f 100644 --- a/web/ASC.Web.Api/Api/PortalController.cs +++ b/web/ASC.Web.Api/Api/PortalController.cs @@ -197,8 +197,10 @@ public class PortalController : ControllerBase return string.Empty; } - return await _commonLinkUtility.GetConfirmationEmailUrlAsync(string.Empty, ConfirmType.LinkInvite, (int)employeeType, _authContext.CurrentAccount.ID) - + $"&emplType={employeeType:d}"; + var link = await _commonLinkUtility.GetConfirmationEmailUrlAsync(string.Empty, ConfirmType.LinkInvite, (int)employeeType, _authContext.CurrentAccount.ID) + + $"&emplType={employeeType:d}"; + + return await _urlShortener.GetShortenLinkAsync(link); } ///