Skip to content

Commit

Permalink
Added creation user with a free role when quota is overflowed
Browse files Browse the repository at this point in the history
  • Loading branch information
MaksimChegulov committed Aug 6, 2024
1 parent bbe68e7 commit 81cc5c0
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 102 deletions.
7 changes: 7 additions & 0 deletions products/ASC.Files/Core/Core/Security/FileSecurity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2236,6 +2236,13 @@ public IDictionary<string, bool> GetFileAccesses<T>(File<T> file, SubjectType su

return result;
}

public static bool IsAvailableAccess(FileShare share, SubjectType subjectType, FolderType roomType)
{
return AvailableRoomAccesses.TryGetValue(roomType, out var availableRoles) &&
availableRoles.TryGetValue(subjectType, out var availableRolesBySubject) &&
availableRolesBySubject.Contains(share);
}

private async Task<List<Guid>> GetUserSubjectsAsync<T>(Guid userId, FileEntry<T> entry)
{
Expand Down
93 changes: 48 additions & 45 deletions products/ASC.Files/Core/Core/VirtualRooms/InvitationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +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 ASC.Core.Billing;
using ASC.MessagingSystem.EF.Context;

namespace ASC.Files.Core.VirtualRooms;
Expand All @@ -34,16 +33,16 @@ public class InvitationService(
CommonLinkUtility commonLinkUtility,
IDaoFactory daoFactory,
InvitationValidator invitationValidator,
ITariffService tariffService,
TenantManager tenantManager,
CountPaidUserChecker countPaidUserChecker,
FileSecurity fileSecurity,
UserManager userManager,
IPSecurity.IPSecurity iPSecurity,
AuthContext authContext,
IDbContextFactory<MessagesContext> dbContextFactory,
FilesMessageService filesMessageService,
DisplayUserSettingsHelper displayUserSettingsHelper)
DisplayUserSettingsHelper displayUserSettingsHelper,
IDistributedLockProvider distributedLockProvider,
UsersInRoomChecker usersInRoomChecker)
{
public string GetInvitationLink(Guid linkId, Guid createdBy)
{
Expand Down Expand Up @@ -123,14 +122,14 @@ async Task<bool> ResolveAccessAsync<T>(Folder<T> folder)
query = query.Where(x => x.Target == data.RoomId);
}

var userId = authContext.CurrentAccount.ID;
var currentUserId = authContext.CurrentAccount.ID;

await foreach(var auditEvent in query.ToAsyncEnumerable())
{
var description = JsonSerializer.Deserialize<List<string>>(auditEvent.DescriptionRaw);
var info = JsonSerializer.Deserialize<EventDescription<JsonElement>>(description.Last());

if (!info.UserIds.Contains(userId) || auditEvent.UserId == userId)
if (!info.UserIds.Contains(currentUserId) || auditEvent.UserId == currentUserId)
{
continue;
}
Expand All @@ -139,32 +138,29 @@ async Task<bool> ResolveAccessAsync<T>(Folder<T> folder)
return false;
}

if (FileSecurity.PaidShares.Contains(data.Share) && await userManager.GetUserTypeAsync(userId) is EmployeeType.User)
if (FileSecurity.PaidShares.Contains(data.Share) && await userManager.GetUserTypeAsync(currentUserId) is EmployeeType.User)
{
data.Share = FileSecurity.GetHighFreeRole(folder.FolderType);

if (data.Share == FileShare.None ||
!FileSecurity.AvailableRoomAccesses.TryGetValue(folder.FolderType, out var availableRoles) ||
!availableRoles.TryGetValue(SubjectType.InvitationLink, out var availableRolesBySubject) ||
!availableRolesBySubject.Contains(data.Share))
if (data.Share == FileShare.None || !FileSecurity.IsAvailableAccess(data.Share, SubjectType.InvitationLink, folder.FolderType))
{
validation.Result = EmailValidationKeyProvider.ValidationResult.QuotaFailed;
return false;
}
}

var user = await userManager.GetUsersAsync(userId);
var user = await userManager.GetUsersAsync(currentUserId);

await fileSecurity.ShareAsync(folder.Id, FileEntryType.Folder, userId, data.Share);
await fileSecurity.ShareAsync(folder.Id, FileEntryType.Folder, currentUserId, data.Share);

switch (entry)
{
case FileEntry<int> entryInt:
await filesMessageService.SendAsync(MessageAction.RoomCreateUser, entryInt, userId, data.Share, null, true,
await filesMessageService.SendAsync(MessageAction.RoomCreateUser, entryInt, currentUserId, data.Share, null, true,
user.DisplayUserName(false, displayUserSettingsHelper));
break;
case FileEntry<string> entryString:
await filesMessageService.SendAsync(MessageAction.RoomCreateUser, entryString, userId, data.Share, null, true,
await filesMessageService.SendAsync(MessageAction.RoomCreateUser, entryString, currentUserId, data.Share, null, true,
user.DisplayUserName(false, displayUserSettingsHelper));
break;
}
Expand All @@ -184,8 +180,6 @@ await filesMessageService.SendAsync(MessageAction.RoomCreateUser, entryString, u

return validation;
}

validation.Result = await GetQuotaBasedResultAsync(data);

if (validation.Result is EmailValidationKeyProvider.ValidationResult.Ok)
{
Expand All @@ -197,17 +191,8 @@ await filesMessageService.SendAsync(MessageAction.RoomCreateUser, entryString, u

return validation;
}

public async Task<InvitationLinkData> GetInvitationDataAsync(string key, string email, EmployeeType employeeType = EmployeeType.All, Guid? userId = default)
{
var data = await GetLinkDataAsync(key, email, employeeType, userId);

data.Result = await GetQuotaBasedResultAsync(data);

return data;
}

private async Task<InvitationLinkData> GetLinkDataAsync(string key, string email, EmployeeType employeeType = EmployeeType.All, Guid? userId = default)
public async Task<InvitationLinkData> GetLinkDataAsync(string key, string email, EmployeeType employeeType = EmployeeType.All, Guid? userId = default)
{
var result = await invitationValidator.ValidateAsync(key, email, employeeType, userId);
var data = new InvitationLinkData
Expand Down Expand Up @@ -244,32 +229,50 @@ private async Task<InvitationLinkData> GetLinkDataAsync(string key, string email

return data;
}

private async Task<EmailValidationKeyProvider.ValidationResult> GetQuotaBasedResultAsync(InvitationLinkData data)
public async Task AddUserToRoomByInviteAsync(InvitationLinkData data, UserInfo user, bool quotaLimit = false)
{
var tenant = await tenantManager.GetCurrentTenantAsync();

// preferential rate does not allow invite users
if ((await tariffService.GetTariffAsync(tenant.Id)).State > TariffState.Paid)
if (data is not { LinkType: InvitationLinkType.CommonToRoom })
{
return EmailValidationKeyProvider.ValidationResult.Invalid;
return;
}

if (data.LinkType is InvitationLinkType.Individual || data.EmployeeType is EmployeeType.User)
{
return data.Result;
}

try
var success = int.TryParse(data.RoomId, out var id);
var tenantId = await tenantManager.GetCurrentTenantIdAsync();

await using (await distributedLockProvider.TryAcquireFairLockAsync(LockKeyHelper.GetUsersInRoomCountCheckKey(tenantId)))
{
await countPaidUserChecker.CheckAppend();
if (success)
{
await AddToRoomAsync(id);
}
else
{
await AddToRoomAsync(data.RoomId);
}
}
catch (TenantQuotaException)

return;

async Task AddToRoomAsync<T>(T roomId)
{
return EmailValidationKeyProvider.ValidationResult.TariffLimit;
}
await usersInRoomChecker.CheckAppend();
var room = await daoFactory.GetFolderDao<T>().GetFolderAsync(roomId);

return data.Result;
if (quotaLimit && FileSecurity.PaidShares.Contains(data.Share))
{
data.Share = FileSecurity.GetHighFreeRole(room.FolderType);
if (data.Share == FileShare.None || !FileSecurity.IsAvailableAccess(data.Share, SubjectType.InvitationLink, room.FolderType))
{
return;
}
}

await fileSecurity.ShareAsync(roomId, FileEntryType.Folder, user.Id, data.Share);

await filesMessageService.SendAsync(MessageAction.RoomCreateUser, room, user.Id, data.Share, null, true,
user.DisplayUserName(false, displayUserSettingsHelper));
}
}

private async Task<(string, string)> GetRoomDataAsync(string roomId, Func<FileEntry, Task<bool>> accessResolver = null)
Expand Down
43 changes: 19 additions & 24 deletions products/ASC.People/Server/Api/ThirdpartyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ public class ThirdpartyController(
StudioNotifyService studioNotifyService,
TenantManager tenantManager,
InvitationService invitationService,
FileSecurity fileSecurity,
UsersInRoomChecker usersInRoomChecker,
IDistributedLockProvider distributedLockProvider,
LoginProfileTransport loginProfileTransport,
EmailValidationKeyModelHelper emailValidationKeyModelHelper)
: ApiControllerBase
Expand Down Expand Up @@ -189,14 +186,15 @@ public async Task SignupAccountAsync(SignupAccountRequestDto inDto)
}

var model = emailValidationKeyModelHelper.GetModel();
var linkData = await invitationService.GetInvitationDataAsync(inDto.Key, inDto.Email, inDto.EmployeeType ?? EmployeeType.RoomAdmin, model?.UiD);
var linkData = await invitationService.GetLinkDataAsync(inDto.Key, inDto.Email, inDto.EmployeeType ?? EmployeeType.RoomAdmin, model?.UiD);

if (!linkData.IsCorrect)
{
throw new SecurityException(FilesCommonResource.ErrorMessage_InvintationLink);
}

var employeeType = linkData.EmployeeType;
bool quotaLimit;

Guid userId;
try
Expand All @@ -205,7 +203,7 @@ public async Task SignupAccountAsync(SignupAccountRequestDto inDto)

var invitedByEmail = linkData.LinkType == InvitationLinkType.Individual;

var newUser = await CreateNewUser(
(var newUser, quotaLimit) = await CreateNewUser(
GetFirstName(inDto, thirdPartyProfile),
GetLastName(inDto, thirdPartyProfile),
GetEmailAddress(inDto, thirdPartyProfile),
Expand Down Expand Up @@ -245,22 +243,7 @@ public async Task SignupAccountAsync(SignupAccountRequestDto inDto)

if (linkData is { LinkType: InvitationLinkType.CommonToRoom })
{
var success = int.TryParse(linkData.RoomId, out var id);
var tenantId = await tenantManager.GetCurrentTenantIdAsync();

await using (await distributedLockProvider.TryAcquireFairLockAsync(LockKeyHelper.GetUsersInRoomCountCheckKey(tenantId)))
{
if (success)
{
await usersInRoomChecker.CheckAppend();
await fileSecurity.ShareAsync(id, FileEntryType.Folder, user.Id, linkData.Share);
}
else
{
await usersInRoomChecker.CheckAppend();
await fileSecurity.ShareAsync(linkData.RoomId, FileEntryType.Folder, user.Id, linkData.Share);
}
}
await invitationService.AddUserToRoomByInviteAsync(linkData, user, quotaLimit);
}
}

Expand All @@ -283,7 +266,7 @@ public async Task UnlinkAccountAsync(string provider)
await messageService.SendAsync(MessageAction.UserUnlinkedSocialAccount, GetMeaningfulProviderName(provider));
}

private async Task<UserInfo> CreateNewUser(string firstName, string lastName, string email, string passwordHash, EmployeeType employeeType, bool fromInviteLink, bool inviteByEmail, string cultureName)
private async Task<(UserInfo, bool)> CreateNewUser(string firstName, string lastName, string email, string passwordHash, EmployeeType employeeType, bool fromInviteLink, bool inviteByEmail, string cultureName)
{
if (SetupInfo.IsSecretEmail(email))
{
Expand All @@ -310,8 +293,20 @@ private async Task<UserInfo> CreateNewUser(string firstName, string lastName, st
{
user.CultureName = cultureName;
}

return await userManagerWrapper.AddUserAsync(user, passwordHash, true, true, employeeType, fromInviteLink, updateExising: inviteByEmail);

var quotaLimit = false;

try
{
user = await userManagerWrapper.AddUserAsync(user, passwordHash, true, true, employeeType, fromInviteLink, updateExising: inviteByEmail);
}
catch (TenantQuotaException)
{
quotaLimit = true;
user = await userManagerWrapper.AddUserAsync(user, passwordHash, true, true, EmployeeType.User, fromInviteLink, updateExising: inviteByEmail);
}

return (user, quotaLimit);
}

private async Task SaveContactImage(Guid userID, string url)
Expand Down
48 changes: 15 additions & 33 deletions products/ASC.People/Server/Api/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,12 @@ public class UserController(
UsersQuotaSyncOperation usersQuotaSyncOperation,
CountPaidUserChecker countPaidUserChecker,
CountUserChecker activeUsersChecker,
UsersInRoomChecker usersInRoomChecker,
IUrlShortener urlShortener,
FileSecurityCommon fileSecurityCommon,
IDistributedLockProvider distributedLockProvider,
QuotaSocketManager quotaSocketManager,
IQuotaService quotaService,
CustomQuota customQuota,
IDaoFactory daoFactory,
FilesMessageService filesMessageService,
AuditEventsRepository auditEventsRepository,
EmailValidationKeyModelHelper emailValidationKeyModelHelper)
: PeopleControllerBase(userManager, permissionContext, apiContext, userPhotoManager, httpClientFactory, httpContextAccessor)
Expand Down Expand Up @@ -172,7 +169,7 @@ public async Task<EmployeeFullDto> AddMember(MemberRequestDto inDto)
{
await _apiContext.AuthByClaimAsync();
var model = emailValidationKeyModelHelper.GetModel();
var linkData = inDto.FromInviteLink ? await invitationService.GetInvitationDataAsync(inDto.Key, inDto.Email, inDto.Type, model?.UiD) : null;
var linkData = inDto.FromInviteLink ? await invitationService.GetLinkDataAsync(inDto.Key, inDto.Email, inDto.Type, model?.UiD) : null;
if (linkData is { IsCorrect: false })
{
throw new SecurityException(FilesCommonResource.ErrorMessage_InvintationLink);
Expand Down Expand Up @@ -249,8 +246,19 @@ public async Task<EmployeeFullDto> AddMember(MemberRequestDto inDto)

cache.Insert("REWRITE_URL" + await tenantManager.GetCurrentTenantIdAsync(), HttpContext.Request.GetDisplayUrl(), TimeSpan.FromMinutes(5));

user = await userManagerWrapper.AddUserAsync(user, inDto.PasswordHash, inDto.FromInviteLink, true, inDto.Type,
inDto.FromInviteLink && linkData is { IsCorrect: true, ConfirmType: not ConfirmType.EmpInvite }, true, true, byEmail);
var quotaLimit = false;

try
{
user = await userManagerWrapper.AddUserAsync(user, inDto.PasswordHash, inDto.FromInviteLink, true, inDto.Type,
inDto.FromInviteLink && linkData is { IsCorrect: true, ConfirmType: not ConfirmType.EmpInvite }, true, true, byEmail);
}
catch (TenantQuotaException)
{
quotaLimit = true;
user = await userManagerWrapper.AddUserAsync(user, inDto.PasswordHash, inDto.FromInviteLink, true, EmployeeType.User,
inDto.FromInviteLink && linkData is { IsCorrect: true, ConfirmType: not ConfirmType.EmpInvite }, true, true, byEmail);
}

await UpdateDepartmentsAsync(inDto.Department, user);

Expand All @@ -261,20 +269,7 @@ public async Task<EmployeeFullDto> AddMember(MemberRequestDto inDto)

if (linkData is { LinkType: InvitationLinkType.CommonToRoom })
{
var success = int.TryParse(linkData.RoomId, out var id);
var tenantId = await tenantManager.GetCurrentTenantIdAsync();

await using (await distributedLockProvider.TryAcquireFairLockAsync(LockKeyHelper.GetUsersInRoomCountCheckKey(tenantId)))
{
if (success)
{
await AddUserToRoomAsync(id);
}
else
{
await AddUserToRoomAsync(linkData.RoomId);
}
}
await invitationService.AddUserToRoomByInviteAsync(linkData, user, quotaLimit);
}

if (inDto.IsUser.GetValueOrDefault(false))
Expand All @@ -287,19 +282,6 @@ public async Task<EmployeeFullDto> AddMember(MemberRequestDto inDto)
}

return await employeeFullDtoHelper.GetFullAsync(user);

async Task AddUserToRoomAsync<T>(T roomId)
{
await usersInRoomChecker.CheckAppend();
var roomTask = daoFactory.GetFolderDao<T>().GetFolderAsync(roomId);

await fileSecurity.ShareAsync(roomId, FileEntryType.Folder, user.Id, linkData.Share);

var room = await roomTask;

await filesMessageService.SendAsync(MessageAction.RoomCreateUser, room, user.Id, linkData.Share, null, true,
user.DisplayUserName(false, displayUserSettingsHelper));
}
}

/// <summary>
Expand Down

0 comments on commit 81cc5c0

Please sign in to comment.