// (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 FileShare = ASC.Files.Core.Security.FileShare; namespace ASC.Files.Api; [ConstraintRoute("int")] public class VirtualRoomsInternalController : VirtualRoomsController { public VirtualRoomsInternalController( FoldersControllerHelper foldersControllerHelper, GlobalFolderHelper globalFolderHelper, FileOperationDtoHelper fileOperationDtoHelper, SecurityControllerHelper securityControllerHelper, CoreBaseSettings coreBaseSettings, AuthContext authContext, RoomInvitationLinksService roomLinksService, CustomTagsService customTagsService, RoomLogoManager roomLogoManager, StudioNotifyService studioNotifyService, FileStorageService fileStorageService, FileSecurity fileSecurity, FileSecurityCommon fileSecurityCommon, EmailValidationKeyProvider emailValidationKeyProvider, FolderDtoHelper folderDtoHelper, FileDtoHelper fileDtoHelper, FileShareDtoHelper fileShareDtoHelper) : base( foldersControllerHelper, globalFolderHelper, fileOperationDtoHelper, securityControllerHelper, coreBaseSettings, authContext, roomLinksService, customTagsService, roomLogoManager, studioNotifyService, fileStorageService, fileSecurity, fileSecurityCommon, emailValidationKeyProvider, folderDtoHelper, fileDtoHelper, fileShareDtoHelper) { } /// /// Create a room in the virtual rooms section /// /// /// Create room /// /// /// Room name /// /// /// Room preset type /// /// /// Room info /// [HttpPost("rooms")] public async Task> CreateRoomAsync(CreateRoomRequestDto inDto) { ErrorIfNotDocSpace(); var room = await _fileStorageService.CreateRoomAsync(inDto.Title, inDto.RoomType, inDto.Private, inDto.Share, inDto.Notify, inDto.SharingMessage); return await _folderDtoHelper.GetAsync(room); } } public class VirtualRoomsThirdPartyController : VirtualRoomsController { public VirtualRoomsThirdPartyController( FoldersControllerHelper foldersControllerHelper, GlobalFolderHelper globalFolderHelper, FileOperationDtoHelper fileOperationDtoHelper, SecurityControllerHelper securityControllerHelper, CoreBaseSettings coreBaseSettings, AuthContext authContext, RoomInvitationLinksService roomLinksService, CustomTagsService customTagsService, RoomLogoManager roomLogoManager, StudioNotifyService studioNotifyService, FileStorageService fileStorageService, FileSecurity fileSecurity, FileSecurityCommon fileSecurityCommon, EmailValidationKeyProvider emailValidationKeyProvider, FolderDtoHelper folderDtoHelper, FileDtoHelper fileDtoHelper, FileShareDtoHelper fileShareDtoHelper) : base( foldersControllerHelper, globalFolderHelper, fileOperationDtoHelper, securityControllerHelper, coreBaseSettings, authContext, roomLinksService, customTagsService, roomLogoManager, studioNotifyService, fileStorageService, fileSecurity, fileSecurityCommon, emailValidationKeyProvider, folderDtoHelper, fileDtoHelper, fileShareDtoHelper) { } /// /// Creates a room in the virtual rooms section stored in a third-party storage /// /// /// Create third-party room /// /// /// ID of the folder in the third-party storage in which the contents of the room will be stored /// /// /// Room name /// /// /// Room preset type /// /// /// Room info /// [HttpPost("rooms/thirdparty/{id}")] public async Task> CreateRoomAsync(string id, CreateRoomRequestDto inDto) { ErrorIfNotDocSpace(); var room = await _fileStorageService.CreateThirdPartyRoomAsync(inDto.Title, inDto.RoomType, id, inDto.Private, inDto.Share, inDto.Notify, inDto.SharingMessage); return await _folderDtoHelper.GetAsync(room); } } public abstract class VirtualRoomsController : ApiControllerBase { private readonly FoldersControllerHelper _foldersControllerHelper; private readonly GlobalFolderHelper _globalFolderHelper; private readonly FileOperationDtoHelper _fileOperationDtoHelper; private readonly SecurityControllerHelper _securityControllerHelper; private readonly CoreBaseSettings _coreBaseSettings; private readonly AuthContext _authContext; private readonly RoomInvitationLinksService _roomLinksService; private readonly CustomTagsService _customTagsService; private readonly RoomLogoManager _roomLogoManager; private readonly StudioNotifyService _studioNotifyService; protected readonly FileStorageService _fileStorageService; private readonly FileSecurity _fileSecurity; private readonly FileSecurityCommon _fileSecurityCommon; private readonly EmailValidationKeyProvider _emailValidationKeyProvider; private readonly FileShareDtoHelper _fileShareDtoHelper; protected VirtualRoomsController( FoldersControllerHelper foldersControllerHelper, GlobalFolderHelper globalFolderHelper, FileOperationDtoHelper fileOperationDtoHelper, SecurityControllerHelper securityControllerHelper, CoreBaseSettings coreBaseSettings, AuthContext authContext, RoomInvitationLinksService roomLinksService, CustomTagsService customTagsService, RoomLogoManager roomLogoManager, StudioNotifyService studioNotifyService, FileStorageService fileStorageService, FileSecurity fileSecurity, FileSecurityCommon fileSecurityCommon, EmailValidationKeyProvider emailValidationKeyProvider, FolderDtoHelper folderDtoHelper, FileDtoHelper fileDtoHelper, FileShareDtoHelper fileShareDtoHelper) : base(folderDtoHelper, fileDtoHelper) { _foldersControllerHelper = foldersControllerHelper; _globalFolderHelper = globalFolderHelper; _fileOperationDtoHelper = fileOperationDtoHelper; _securityControllerHelper = securityControllerHelper; _coreBaseSettings = coreBaseSettings; _authContext = authContext; _roomLinksService = roomLinksService; _customTagsService = customTagsService; _roomLogoManager = roomLogoManager; _studioNotifyService = studioNotifyService; _fileStorageService = fileStorageService; _fileSecurity = fileSecurity; _fileSecurityCommon = fileSecurityCommon; _emailValidationKeyProvider = emailValidationKeyProvider; _fileShareDtoHelper = fileShareDtoHelper; } /// /// Getting virtual room information /// /// /// Room ID /// /// /// Room info /// [HttpGet("rooms/{id}")] public async Task> GetRoomInfoAsync(T id) { ErrorIfNotDocSpace(); var folder = await _fileStorageService.GetFolderAsync(id).NotFoundIfNull("Folder not found"); return await _folderDtoHelper.GetAsync(folder); } /// /// Renaming a virtual room /// /// /// Room ID /// /// /// New room name /// /// /// Updated room info /// [HttpPut("rooms/{id}")] public async Task> UpdateRoomAsync(T id, UpdateRoomRequestDto inDto) { ErrorIfNotDocSpace(); var room = await _fileStorageService.FolderRenameAsync(id, inDto.Title); return await _folderDtoHelper.GetAsync(room); } /// /// Permanent deletion of a virtual room /// /// /// Deleting room /// /// /// Room ID /// /// /// Delete after finished /// /// /// Result of the operation /// [HttpDelete("rooms/{id}")] public async Task DeleteRoomAsync(T id, DeleteRoomRequestDto inDto) { ErrorIfNotDocSpace(); var operationResult = _fileStorageService.DeleteFolder("delete", id, false, inDto.DeleteAfter, true) .FirstOrDefault(); return await _fileOperationDtoHelper.GetAsync(operationResult); } /// /// Moving a room to the archive section /// /// /// Archiving room /// /// /// Room ID /// /// /// Archive after finished /// /// /// Result of the operation /// [HttpPut("rooms/{id}/archive")] public async Task ArchiveRoomAsync(T id, ArchiveRoomRequestDto inDto) { ErrorIfNotDocSpace(); var destFolder = JsonSerializer.SerializeToElement(await _globalFolderHelper.FolderArchiveAsync); var movableRoom = JsonSerializer.SerializeToElement(id); var operationResult = _fileStorageService.MoveOrCopyItems(new List { movableRoom }, new List(), destFolder, FileConflictResolveType.Skip, false, inDto.DeleteAfter) .FirstOrDefault(); return await _fileOperationDtoHelper.GetAsync(operationResult); } /// /// Moving a room to the virtual room section /// /// /// Unarchiving room /// /// /// Room ID /// /// /// Unarchive after finished /// /// /// Result of the operation /// [HttpPut("rooms/{id}/unarchive")] public async Task UnarchiveRoomAsync(T id, ArchiveRoomRequestDto inDto) { ErrorIfNotDocSpace(); var destFolder = JsonSerializer.SerializeToElement(await _globalFolderHelper.FolderVirtualRoomsAsync); var movableRoom = JsonSerializer.SerializeToElement(id); var operationResult = _fileStorageService.MoveOrCopyItems(new List { movableRoom }, new List(), destFolder, FileConflictResolveType.Skip, false, inDto.DeleteAfter) .FirstOrDefault(); return await _fileOperationDtoHelper.GetAsync(operationResult); } /// /// Setting access rights for a virtual room /// /// /// Set access /// /// /// Room ID /// /// /// ID of the user to whom access will be assigned /// /// /// Access level /// /// /// Notifying users about access /// /// /// Notification message /// /// /// Room security info /// [HttpPut("rooms/{id}/share")] public async IAsyncEnumerable SetRoomSecurityAsync(T id, SecurityInfoRequestDto inDto) { ErrorIfNotDocSpace(); IAsyncEnumerable result; if (!string.IsNullOrEmpty(inDto.Key)) { result = SetRoomSecurityByLinkAsync(id, _authContext.CurrentAccount.ID, inDto.Access, inDto.Key); } else { result = _securityControllerHelper.SetFolderSecurityInfoAsync(id, inDto.Share, inDto.Notify, inDto.SharingMessage); } await foreach (var r in result) { yield return r; } } /// /// Getting security information about a room /// /// /// Room ID /// /// Room security info [HttpGet("rooms/{id}/share")] public async IAsyncEnumerable GetRoomSecurityInfo(T id) { var fileShares = await _fileStorageService.GetSharedInfoAsync(Array.Empty(), new[] { id }); foreach (var fileShareDto in fileShares) { yield return await _fileShareDtoHelper.Get(fileShareDto); } } /// /// Getting an invitation link to a virtual room /// /// /// Get invitation link /// /// /// Room ID /// /// /// Access level /// /// /// Invitation link /// [HttpGet("rooms/{id}/links")] public async Task GetInvitationLinkAsync(T id, FileShare access) { ErrorIfNotDocSpace(); await ErrorIfNotRights(id, access); return _roomLinksService.GenerateLink(id, (int)access, EmployeeType.User, _authContext.CurrentAccount.ID); } /// /// Inviting users to the virtual room by email /// /// /// Send invitation link /// /// /// Room ID /// /// /// Mailbox addresses /// /// /// Invitations result /// [HttpPut("rooms/{id}/links/send")] public async Task> SendInvitesToRoomByEmail(T id, InviteUsersByEmailRequestDto inDto) { ErrorIfNotDocSpace(); await ErrorIfNotRights(id, inDto.Access); var results = new List(); foreach (var email in inDto.Emails) { var result = new InviteResultDto { Email = email }; try { var link = _roomLinksService.GenerateLink(id, email, (int)inDto.Access, inDto.EmployeeType, _authContext.CurrentAccount.ID); _studioNotifyService.SendEmailRoomInvite(email, link); result.Success = true; } catch (Exception e) { result.Success = false; result.Message = e.Message; } results.Add(result); } return results; } /// /// Add tags for a virtual room /// /// /// Add tags /// /// /// Room ID /// /// /// Tag names /// /// /// Room info /// [HttpPut("rooms/{id}/tags")] public async Task> AddTagsAsync(T id, BatchTagsRequestDto inDto) { ErrorIfNotDocSpace(); var room = await _customTagsService.AddRoomTagsAsync(id, inDto.Names); return await _folderDtoHelper.GetAsync(room); } /// /// Attaching a tag to a virtual room /// /// /// Add tags /// /// /// Room ID /// /// /// Tag names /// /// /// Room info /// [HttpDelete("rooms/{id}/tags")] public async Task> DeleteTagsAsync(T id, BatchTagsRequestDto inDto) { ErrorIfNotDocSpace(); var room = await _customTagsService.DeleteRoomTagsAsync(id, inDto.Names); return await _folderDtoHelper.GetAsync(room); } /// /// Creating a logo for a virtual room /// /// /// Create room logo /// /// /// Room ID /// /// /// The path to the temporary image file /// /// /// The coordinate X of the rectangle's starting point /// /// /// The coordinate Y of the rectangle's starting point /// /// /// Rectangle width /// /// /// Rectangle height /// /// /// Room info /// [HttpPost("rooms/{id}/logo")] public async Task> CreateRoomLogoAsync(T id, LogoRequestDto inDto) { ErrorIfNotDocSpace(); var room = await _roomLogoManager.CreateAsync(id, inDto.TmpFile, inDto.X, inDto.Y, inDto.Width, inDto.Height); return await _folderDtoHelper.GetAsync(room); } /// /// Removing the virtual room logo /// /// /// Remove room logo /// /// /// Room ID /// /// /// Room info /// [HttpDelete("rooms/{id}/logo")] public async Task> DeleteRoomLogoAsync(T id) { ErrorIfNotDocSpace(); var room = await _roomLogoManager.DeleteAsync(id); return await _folderDtoHelper.GetAsync(room); } /// /// Pins a virtual room to the list /// /// /// Pin room /// /// /// Room ID /// /// /// Room info /// [HttpPut("rooms/{id}/pin")] public async Task> PinRoomAsync(T id) { ErrorIfNotDocSpace(); var room = await _fileStorageService.SetPinnedStatusAsync(id, true); return await _folderDtoHelper.GetAsync(room); } /// /// Unpins a virtual room to the list /// /// /// Unpin room /// /// /// Room ID /// /// /// Room info /// [HttpPut("rooms/{id}/unpin")] public async Task> UnpinRoomAsync(T id) { ErrorIfNotDocSpace(); var room = await _fileStorageService.SetPinnedStatusAsync(id, false); return await _folderDtoHelper.GetAsync(room); } protected void ErrorIfNotDocSpace() { if (_coreBaseSettings.DisableDocSpace) { throw new NotSupportedException(); } } private async IAsyncEnumerable SetRoomSecurityByLinkAsync(T id, Guid userId, FileShare access, string key) { var result = _emailValidationKeyProvider.ValidateEmailKey(string.Empty + ConfirmType.LinkInvite + ((int)EmployeeType.User + (int)access + id.ToString()), key, _emailValidationKeyProvider.ValidEmailKeyInterval); if (result != EmailValidationKeyProvider.ValidationResult.Ok) { throw new InvalidDataException(); } var share = new FileShareParams { ShareTo = userId, Access = access }; await foreach (var s in _securityControllerHelper.SetFolderSecurityInfoAsync(id, new[] { share }, false, null, true)) { yield return s; } } private async Task ErrorIfNotRights(T id, FileShare share) { var room = await _fileStorageService.GetFolderAsync(id); if ((share == FileShare.RoomManager && !_fileSecurityCommon.IsAdministrator(_authContext.CurrentAccount.ID)) || !await _fileSecurity.CanEditRoomAsync(room)) { throw new InvalidOperationException("You don't have the rights to invite users to the room"); } } } public class VirtualRoomsCommonController : ApiControllerBase { private readonly FileStorageService _fileStorageService; private readonly FolderContentDtoHelper _folderContentDtoHelper; private readonly GlobalFolderHelper _globalFolderHelper; private readonly CoreBaseSettings _coreBaseSettings; private readonly ApiContext _apiContext; private readonly CustomTagsService _customTagsService; private readonly RoomLogoManager _roomLogoManager; private readonly SetupInfo _setupInfo; private readonly FileSizeComment _fileSizeComment; public VirtualRoomsCommonController(FileStorageService fileStorageService, FolderContentDtoHelper folderContentDtoHelper, GlobalFolderHelper globalFolderHelper, CoreBaseSettings coreBaseSettings, ApiContext apiContext, CustomTagsService customTagsService, RoomLogoManager roomLogoManager, SetupInfo setupInfo, FileSizeComment fileSizeComment, FolderDtoHelper folderDtoHelper, FileDtoHelper fileDtoHelper) : base(folderDtoHelper, fileDtoHelper) { _fileStorageService = fileStorageService; _folderContentDtoHelper = folderContentDtoHelper; _globalFolderHelper = globalFolderHelper; _coreBaseSettings = coreBaseSettings; _apiContext = apiContext; _customTagsService = customTagsService; _roomLogoManager = roomLogoManager; _setupInfo = setupInfo; _fileSizeComment = fileSizeComment; } /// /// Getting the contents of the virtual rooms section /// /// /// Get rooms /// /// /// The value of the beginning of the enumeration /// /// /// Quantity /// /// /// Filter by name /// /// /// Filter by room type /// /// /// Filter by user ID /// /// /// Full-text content search /// /// /// Search by subfolders /// /// /// Room search area /// /// /// Search by rooms without tags /// /// /// Filter by tags /// /// /// Exclude your rooms from search /// /// /// Virtual Rooms content /// [HttpGet("rooms")] public async Task> GetRoomsFolderAsync(RoomFilterType? type, string subjectId, bool? searchInContent, bool? withSubfolders, SearchArea? searchArea, bool? withoutTags, string tags, bool? withoutMe) { ErrorIfNotDocSpace(); var parentId = searchArea != SearchArea.Archive ? await _globalFolderHelper.GetFolderVirtualRooms() : await _globalFolderHelper.GetFolderArchive(); var filter = type switch { RoomFilterType.FillingFormsRoomOnly => FilterType.FillingFormsRooms, RoomFilterType.ReadOnlyRoomOnly => FilterType.ReadOnlyRooms, RoomFilterType.EditingRoomOnly => FilterType.EditingRooms, RoomFilterType.ReviewRoomOnly => FilterType.ReviewRooms, RoomFilterType.CustomRoomOnly => FilterType.CustomRooms, RoomFilterType.FoldersOnly => FilterType.FoldersOnly, _ => FilterType.None }; var tagNames = !string.IsNullOrEmpty(tags) ? JsonSerializer.Deserialize>(tags) : null; OrderBy orderBy = null; if (SortedByTypeExtensions.TryParse(_apiContext.SortBy, true, out var sortBy)) { orderBy = new OrderBy(sortBy, !_apiContext.SortDescending); } var startIndex = Convert.ToInt32(_apiContext.StartIndex); var count = Convert.ToInt32(_apiContext.Count); var filterValue = _apiContext.FilterValue; var content = await _fileStorageService.GetFolderItemsAsync(parentId, startIndex, count, filter, false, subjectId, filterValue, searchInContent ?? false, withSubfolders ?? false, orderBy, searchArea ?? SearchArea.Active, withoutTags ?? false, tagNames, withoutMe ?? false); var dto = await _folderContentDtoHelper.GetAsync(content, startIndex); return dto.NotFoundIfNull(); } /// /// Create a custom tag /// /// /// Create tag /// /// /// Tag name /// /// /// Tag name /// [HttpPost("tags")] public async Task CreateTagAsync(CreateTagRequestDto inDto) { ErrorIfNotDocSpace(); return await _customTagsService.CreateTagAsync(inDto.Name); } /// /// Getting a list of custom tags /// /// /// Get tags /// /// /// The value of the beginning of the enumeration /// /// /// Quantity /// /// /// Filter by name /// /// /// Tag names /// [HttpGet("tags")] public async IAsyncEnumerable GetTagsInfoAsync() { ErrorIfNotDocSpace(); var from = Convert.ToInt32(_apiContext.StartIndex); var count = Convert.ToInt32(_apiContext.Count); await foreach (var tag in _customTagsService.GetTagsInfoAsync(_apiContext.FilterValue, TagType.Custom, from, count)) { yield return tag; } } /// /// Delete a bunch of custom tags /// /// /// Delete tags /// /// /// Void /// [HttpDelete("tags")] public async Task DeleteTagsAsync(BatchTagsRequestDto inDto) { ErrorIfNotDocSpace(); await _customTagsService.DeleteTagsAsync(inDto.Names); } /// /// Upload a temporary image to create a virtual room logo /// /// /// Upload image for room logo /// /// /// Image data /// /// /// Upload result /// [HttpPost("logos")] public async Task UploadRoomLogo(IFormCollection formCollection) { var result = new UploadResultDto(); try { if (formCollection.Files.Count != 0) { var roomLogo = formCollection.Files[0]; if (roomLogo.Length > _setupInfo.MaxImageUploadSize) { result.Success = false; result.Message = _fileSizeComment.FileImageSizeExceptionString; return result; } var data = new byte[roomLogo.Length]; using var inputStream = roomLogo.OpenReadStream(); var br = new BinaryReader(inputStream); br.Read(data, 0, (int)roomLogo.Length); br.Close(); UserPhotoThumbnailManager.CheckImgFormat(data); result.Data = await _roomLogoManager.SaveTempAsync(data, _setupInfo.MaxImageUploadSize); result.Success = true; } else { result.Success = false; } } catch (Exception ex) { result.Success = false; result.Message = ex.Message; } return result; } private void ErrorIfNotDocSpace() { if (_coreBaseSettings.DisableDocSpace) { throw new NotSupportedException(); } } }