diff --git a/src/Api/Auth/Controllers/EmergencyAccessController.cs b/src/Api/Auth/Controllers/EmergencyAccessController.cs index 9f8ea3df01..5d1f47de73 100644 --- a/src/Api/Auth/Controllers/EmergencyAccessController.cs +++ b/src/Api/Auth/Controllers/EmergencyAccessController.cs @@ -167,7 +167,7 @@ public class EmergencyAccessController : Controller { var user = await _userService.GetUserByPrincipalAsync(User); var viewResult = await _emergencyAccessService.ViewAsync(id, user); - return new EmergencyAccessViewResponseModel(_globalSettings, viewResult.EmergencyAccess, viewResult.Ciphers); + return new EmergencyAccessViewResponseModel(_globalSettings, viewResult.EmergencyAccess, viewResult.Ciphers, user); } [HttpGet("{id}/{cipherId}/attachment/{attachmentId}")] diff --git a/src/Api/Auth/Models/Response/EmergencyAccessResponseModel.cs b/src/Api/Auth/Models/Response/EmergencyAccessResponseModel.cs index a72f3cf03f..2fb9a67199 100644 --- a/src/Api/Auth/Models/Response/EmergencyAccessResponseModel.cs +++ b/src/Api/Auth/Models/Response/EmergencyAccessResponseModel.cs @@ -116,11 +116,17 @@ public class EmergencyAccessViewResponseModel : ResponseModel public EmergencyAccessViewResponseModel( IGlobalSettings globalSettings, EmergencyAccess emergencyAccess, - IEnumerable ciphers) + IEnumerable ciphers, + User user) : base("emergencyAccessView") { KeyEncrypted = emergencyAccess.KeyEncrypted; - Ciphers = ciphers.Select(c => new CipherResponseModel(c, globalSettings)); + Ciphers = ciphers.Select(cipher => + new CipherResponseModel( + cipher, + user, + organizationAbilities: null, // Emergency access only retrieves personal ciphers so organizationAbilities is not needed + globalSettings)); } public string KeyEncrypted { get; set; } diff --git a/src/Api/Vault/Controllers/CiphersController.cs b/src/Api/Vault/Controllers/CiphersController.cs index 5a7d427963..62f07005ee 100644 --- a/src/Api/Vault/Controllers/CiphersController.cs +++ b/src/Api/Vault/Controllers/CiphersController.cs @@ -79,14 +79,16 @@ public class CiphersController : Controller [HttpGet("{id}")] public async Task Get(Guid id) { - var userId = _userService.GetProperUserId(User).Value; - var cipher = await GetByIdAsync(id, userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = await GetByIdAsync(id, user.Id); if (cipher == null) { throw new NotFoundException(); } - return new CipherResponseModel(cipher, _globalSettings); + var organizationAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + + return new CipherResponseModel(cipher, user, organizationAbilities, _globalSettings); } [HttpGet("{id}/admin")] @@ -109,32 +111,37 @@ public class CiphersController : Controller [HttpGet("{id}/details")] public async Task GetDetails(Guid id) { - var userId = _userService.GetProperUserId(User).Value; - var cipher = await GetByIdAsync(id, userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = await GetByIdAsync(id, user.Id); if (cipher == null) { throw new NotFoundException(); } - var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id); - return new CipherDetailsResponseModel(cipher, _globalSettings, collectionCiphers); + var organizationAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(user.Id, id); + return new CipherDetailsResponseModel(cipher, user, organizationAbilities, _globalSettings, collectionCiphers); } [HttpGet("")] public async Task> Get() { - var userId = _userService.GetProperUserId(User).Value; + var user = await _userService.GetUserByPrincipalAsync(User); var hasOrgs = _currentContext.Organizations?.Any() ?? false; // TODO: Use hasOrgs proper for cipher listing here? - var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: true || hasOrgs); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, withOrganizations: true || hasOrgs); Dictionary> collectionCiphersGroupDict = null; if (hasOrgs) { - var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(userId); + var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(user.Id); collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key); } - - var responses = ciphers.Select(c => new CipherDetailsResponseModel(c, _globalSettings, + var organizationAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + var responses = ciphers.Select(cipher => new CipherDetailsResponseModel( + cipher, + user, + organizationAbilities, + _globalSettings, collectionCiphersGroupDict)).ToList(); return new ListResponseModel(responses); } @@ -142,30 +149,38 @@ public class CiphersController : Controller [HttpPost("")] public async Task Post([FromBody] CipherRequestModel model) { - var userId = _userService.GetProperUserId(User).Value; - var cipher = model.ToCipherDetails(userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = model.ToCipherDetails(user.Id); if (cipher.OrganizationId.HasValue && !await _currentContext.OrganizationUser(cipher.OrganizationId.Value)) { throw new NotFoundException(); } - await _cipherService.SaveDetailsAsync(cipher, userId, model.LastKnownRevisionDate, null, cipher.OrganizationId.HasValue); - var response = new CipherResponseModel(cipher, _globalSettings); + await _cipherService.SaveDetailsAsync(cipher, user.Id, model.LastKnownRevisionDate, null, cipher.OrganizationId.HasValue); + var response = new CipherResponseModel( + cipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings); return response; } [HttpPost("create")] public async Task PostCreate([FromBody] CipherCreateRequestModel model) { - var userId = _userService.GetProperUserId(User).Value; - var cipher = model.Cipher.ToCipherDetails(userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = model.Cipher.ToCipherDetails(user.Id); if (cipher.OrganizationId.HasValue && !await _currentContext.OrganizationUser(cipher.OrganizationId.Value)) { throw new NotFoundException(); } - await _cipherService.SaveDetailsAsync(cipher, userId, model.Cipher.LastKnownRevisionDate, model.CollectionIds, cipher.OrganizationId.HasValue); - var response = new CipherResponseModel(cipher, _globalSettings); + await _cipherService.SaveDetailsAsync(cipher, user.Id, model.Cipher.LastKnownRevisionDate, model.CollectionIds, cipher.OrganizationId.HasValue); + var response = new CipherResponseModel( + cipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings); return response; } @@ -191,8 +206,8 @@ public class CiphersController : Controller [HttpPost("{id}")] public async Task Put(Guid id, [FromBody] CipherRequestModel model) { - var userId = _userService.GetProperUserId(User).Value; - var cipher = await GetByIdAsync(id, userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = await GetByIdAsync(id, user.Id); if (cipher == null) { throw new NotFoundException(); @@ -200,7 +215,7 @@ public class CiphersController : Controller ValidateClientVersionForFido2CredentialSupport(cipher); - var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList(); + var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(user.Id, id)).Select(c => c.CollectionId).ToList(); var modelOrgId = string.IsNullOrWhiteSpace(model.OrganizationId) ? (Guid?)null : new Guid(model.OrganizationId); if (cipher.OrganizationId != modelOrgId) @@ -209,9 +224,13 @@ public class CiphersController : Controller "then try again."); } - await _cipherService.SaveDetailsAsync(model.ToCipherDetails(cipher), userId, model.LastKnownRevisionDate, collectionIds); + await _cipherService.SaveDetailsAsync(model.ToCipherDetails(cipher), user.Id, model.LastKnownRevisionDate, collectionIds); - var response = new CipherResponseModel(cipher, _globalSettings); + var response = new CipherResponseModel( + cipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings); return response; } @@ -278,7 +297,14 @@ public class CiphersController : Controller })); } - var responses = ciphers.Select(c => new CipherDetailsResponseModel(c, _globalSettings)); + var user = await _userService.GetUserByPrincipalAsync(User); + var organizationAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + var responses = ciphers.Select(cipher => + new CipherDetailsResponseModel( + cipher, + user, + organizationAbilities, + _globalSettings)); return new ListResponseModel(responses); } @@ -572,12 +598,16 @@ public class CiphersController : Controller [HttpPost("{id}/partial")] public async Task PutPartial(Guid id, [FromBody] CipherPartialRequestModel model) { - var userId = _userService.GetProperUserId(User).Value; + var user = await _userService.GetUserByPrincipalAsync(User); var folderId = string.IsNullOrWhiteSpace(model.FolderId) ? null : (Guid?)new Guid(model.FolderId); - await _cipherRepository.UpdatePartialAsync(id, userId, folderId, model.Favorite); + await _cipherRepository.UpdatePartialAsync(id, user.Id, folderId, model.Favorite); - var cipher = await GetByIdAsync(id, userId); - var response = new CipherResponseModel(cipher, _globalSettings); + var cipher = await GetByIdAsync(id, user.Id); + var response = new CipherResponseModel( + cipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings); return response; } @@ -585,9 +615,9 @@ public class CiphersController : Controller [HttpPost("{id}/share")] public async Task PutShare(Guid id, [FromBody] CipherShareRequestModel model) { - var userId = _userService.GetProperUserId(User).Value; + var user = await _userService.GetUserByPrincipalAsync(User); var cipher = await _cipherRepository.GetByIdAsync(id); - if (cipher == null || cipher.UserId != userId || + if (cipher == null || cipher.UserId != user.Id || !await _currentContext.OrganizationUser(new Guid(model.Cipher.OrganizationId))) { throw new NotFoundException(); @@ -597,10 +627,14 @@ public class CiphersController : Controller var original = cipher.Clone(); await _cipherService.ShareAsync(original, model.Cipher.ToCipher(cipher), new Guid(model.Cipher.OrganizationId), - model.CollectionIds.Select(c => new Guid(c)), userId, model.Cipher.LastKnownRevisionDate); + model.CollectionIds.Select(c => new Guid(c)), user.Id, model.Cipher.LastKnownRevisionDate); - var sharedCipher = await GetByIdAsync(id, userId); - var response = new CipherResponseModel(sharedCipher, _globalSettings); + var sharedCipher = await GetByIdAsync(id, user.Id); + var response = new CipherResponseModel( + sharedCipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings); return response; } @@ -608,8 +642,8 @@ public class CiphersController : Controller [HttpPost("{id}/collections")] public async Task PutCollections(Guid id, [FromBody] CipherCollectionsRequestModel model) { - var userId = _userService.GetProperUserId(User).Value; - var cipher = await GetByIdAsync(id, userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = await GetByIdAsync(id, user.Id); if (cipher == null || !cipher.OrganizationId.HasValue || !await _currentContext.OrganizationUser(cipher.OrganizationId.Value)) { @@ -617,20 +651,25 @@ public class CiphersController : Controller } await _cipherService.SaveCollectionsAsync(cipher, - model.CollectionIds.Select(c => new Guid(c)), userId, false); + model.CollectionIds.Select(c => new Guid(c)), user.Id, false); - var updatedCipher = await GetByIdAsync(id, userId); - var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id); + var updatedCipher = await GetByIdAsync(id, user.Id); + var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(user.Id, id); - return new CipherDetailsResponseModel(updatedCipher, _globalSettings, collectionCiphers); + return new CipherDetailsResponseModel( + updatedCipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings, + collectionCiphers); } [HttpPut("{id}/collections_v2")] [HttpPost("{id}/collections_v2")] public async Task PutCollections_vNext(Guid id, [FromBody] CipherCollectionsRequestModel model) { - var userId = _userService.GetProperUserId(User).Value; - var cipher = await GetByIdAsync(id, userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = await GetByIdAsync(id, user.Id); if (cipher == null || !cipher.OrganizationId.HasValue || !await _currentContext.OrganizationUser(cipher.OrganizationId.Value) || !cipher.ViewPassword) { @@ -638,10 +677,10 @@ public class CiphersController : Controller } await _cipherService.SaveCollectionsAsync(cipher, - model.CollectionIds.Select(c => new Guid(c)), userId, false); + model.CollectionIds.Select(c => new Guid(c)), user.Id, false); - var updatedCipher = await GetByIdAsync(id, userId); - var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id); + var updatedCipher = await GetByIdAsync(id, user.Id); + var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(user.Id, id); // If a user removes the last Can Manage access of a cipher, the "updatedCipher" will return null // We will be returning an "Unavailable" property so the client knows the user can no longer access this var response = new OptionalCipherDetailsResponseModel() @@ -649,7 +688,12 @@ public class CiphersController : Controller Unavailable = updatedCipher is null, Cipher = updatedCipher is null ? null - : new CipherDetailsResponseModel(updatedCipher, _globalSettings, collectionCiphers) + : new CipherDetailsResponseModel( + updatedCipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings, + collectionCiphers) }; return response; } @@ -839,15 +883,19 @@ public class CiphersController : Controller [HttpPut("{id}/restore")] public async Task PutRestore(Guid id) { - var userId = _userService.GetProperUserId(User).Value; - var cipher = await GetByIdAsync(id, userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = await GetByIdAsync(id, user.Id); if (cipher == null) { throw new NotFoundException(); } - await _cipherService.RestoreAsync(cipher, userId); - return new CipherResponseModel(cipher, _globalSettings); + await _cipherService.RestoreAsync(cipher, user.Id); + return new CipherResponseModel( + cipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings); } [HttpPut("{id}/restore-admin")] @@ -996,10 +1044,10 @@ public class CiphersController : Controller [HttpPost("{id}/attachment/v2")] public async Task PostAttachment(Guid id, [FromBody] AttachmentRequestModel request) { - var userId = _userService.GetProperUserId(User).Value; + var user = await _userService.GetUserByPrincipalAsync(User); var cipher = request.AdminRequest ? await _cipherRepository.GetOrganizationDetailsByIdAsync(id) : - await GetByIdAsync(id, userId); + await GetByIdAsync(id, user.Id); if (cipher == null || (request.AdminRequest && (!cipher.OrganizationId.HasValue || !await CanEditCipherAsAdminAsync(cipher.OrganizationId.Value, new[] { cipher.Id })))) @@ -1013,13 +1061,17 @@ public class CiphersController : Controller } var (attachmentId, uploadUrl) = await _cipherService.CreateAttachmentForDelayedUploadAsync(cipher, - request.Key, request.FileName, request.FileSize, request.AdminRequest, userId); + request.Key, request.FileName, request.FileSize, request.AdminRequest, user.Id); return new AttachmentUploadDataResponseModel { AttachmentId = attachmentId, Url = uploadUrl, FileUploadType = _attachmentStorageService.FileUploadType, - CipherResponse = request.AdminRequest ? null : new CipherResponseModel((CipherDetails)cipher, _globalSettings), + CipherResponse = request.AdminRequest ? null : new CipherResponseModel( + (CipherDetails)cipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings), CipherMiniResponse = request.AdminRequest ? new CipherMiniResponseModel(cipher, _globalSettings, cipher.OrganizationUseTotp) : null, }; } @@ -1077,8 +1129,8 @@ public class CiphersController : Controller { ValidateAttachment(); - var userId = _userService.GetProperUserId(User).Value; - var cipher = await GetByIdAsync(id, userId); + var user = await _userService.GetUserByPrincipalAsync(User); + var cipher = await GetByIdAsync(id, user.Id); if (cipher == null) { throw new NotFoundException(); @@ -1087,10 +1139,14 @@ public class CiphersController : Controller await Request.GetFileAsync(async (stream, fileName, key) => { await _cipherService.CreateAttachmentAsync(cipher, stream, fileName, key, - Request.ContentLength.GetValueOrDefault(0), userId); + Request.ContentLength.GetValueOrDefault(0), user.Id); }); - return new CipherResponseModel(cipher, _globalSettings); + return new CipherResponseModel( + cipher, + user, + await _applicationCacheService.GetOrganizationAbilitiesAsync(), + _globalSettings); } [HttpPost("{id}/attachment-admin")] diff --git a/src/Api/Vault/Controllers/SyncController.cs b/src/Api/Vault/Controllers/SyncController.cs index c08a5f86e0..1b8978fc65 100644 --- a/src/Api/Vault/Controllers/SyncController.cs +++ b/src/Api/Vault/Controllers/SyncController.cs @@ -36,6 +36,7 @@ public class SyncController : Controller private readonly ICurrentContext _currentContext; private readonly Version _sshKeyCipherMinimumVersion = new(Constants.SSHKeyCipherMinimumVersion); private readonly IFeatureService _featureService; + private readonly IApplicationCacheService _applicationCacheService; public SyncController( IUserService userService, @@ -49,7 +50,8 @@ public class SyncController : Controller ISendRepository sendRepository, GlobalSettings globalSettings, ICurrentContext currentContext, - IFeatureService featureService) + IFeatureService featureService, + IApplicationCacheService applicationCacheService) { _userService = userService; _folderRepository = folderRepository; @@ -63,6 +65,7 @@ public class SyncController : Controller _globalSettings = globalSettings; _currentContext = currentContext; _featureService = featureService; + _applicationCacheService = applicationCacheService; } [HttpGet("")] @@ -104,7 +107,9 @@ public class SyncController : Controller var organizationManagingActiveUser = await _userService.GetOrganizationsManagingUserAsync(user.Id); var organizationIdsManagingActiveUser = organizationManagingActiveUser.Select(o => o.Id); - var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization, + var organizationAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + + var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationAbilities, organizationIdsManagingActiveUser, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails, folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends); return response; diff --git a/src/Api/Vault/Models/Response/CipherPermissionsResponseModel.cs b/src/Api/Vault/Models/Response/CipherPermissionsResponseModel.cs new file mode 100644 index 0000000000..4f2f7e86b2 --- /dev/null +++ b/src/Api/Vault/Models/Response/CipherPermissionsResponseModel.cs @@ -0,0 +1,27 @@ +using Bit.Core.Entities; +using Bit.Core.Models.Data.Organizations; +using Bit.Core.Vault.Authorization.Permissions; +using Bit.Core.Vault.Models.Data; + +namespace Bit.Api.Vault.Models.Response; + +public record CipherPermissionsResponseModel +{ + public bool Delete { get; init; } + public bool Restore { get; init; } + + public CipherPermissionsResponseModel( + User user, + CipherDetails cipherDetails, + IDictionary organizationAbilities) + { + OrganizationAbility organizationAbility = null; + if (cipherDetails.OrganizationId.HasValue && !organizationAbilities.TryGetValue(cipherDetails.OrganizationId.Value, out organizationAbility)) + { + throw new Exception("OrganizationAbility not found for organization cipher."); + } + + Delete = NormalCipherPermissions.CanDelete(user, cipherDetails, organizationAbility); + Restore = NormalCipherPermissions.CanRestore(user, cipherDetails, organizationAbility); + } +} diff --git a/src/Api/Vault/Models/Response/CipherResponseModel.cs b/src/Api/Vault/Models/Response/CipherResponseModel.cs index 207017227a..358da3e62a 100644 --- a/src/Api/Vault/Models/Response/CipherResponseModel.cs +++ b/src/Api/Vault/Models/Response/CipherResponseModel.cs @@ -1,6 +1,7 @@ using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Models.Api; +using Bit.Core.Models.Data.Organizations; using Bit.Core.Settings; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Enums; @@ -96,26 +97,37 @@ public class CipherMiniResponseModel : ResponseModel public class CipherResponseModel : CipherMiniResponseModel { - public CipherResponseModel(CipherDetails cipher, IGlobalSettings globalSettings, string obj = "cipher") + public CipherResponseModel( + CipherDetails cipher, + User user, + IDictionary organizationAbilities, + IGlobalSettings globalSettings, + string obj = "cipher") : base(cipher, globalSettings, cipher.OrganizationUseTotp, obj) { FolderId = cipher.FolderId; Favorite = cipher.Favorite; Edit = cipher.Edit; ViewPassword = cipher.ViewPassword; + Permissions = new CipherPermissionsResponseModel(user, cipher, organizationAbilities); } public Guid? FolderId { get; set; } public bool Favorite { get; set; } public bool Edit { get; set; } public bool ViewPassword { get; set; } + public CipherPermissionsResponseModel Permissions { get; set; } } public class CipherDetailsResponseModel : CipherResponseModel { - public CipherDetailsResponseModel(CipherDetails cipher, GlobalSettings globalSettings, + public CipherDetailsResponseModel( + CipherDetails cipher, + User user, + IDictionary organizationAbilities, + GlobalSettings globalSettings, IDictionary> collectionCiphers, string obj = "cipherDetails") - : base(cipher, globalSettings, obj) + : base(cipher, user, organizationAbilities, globalSettings, obj) { if (collectionCiphers?.ContainsKey(cipher.Id) ?? false) { @@ -127,15 +139,24 @@ public class CipherDetailsResponseModel : CipherResponseModel } } - public CipherDetailsResponseModel(CipherDetails cipher, GlobalSettings globalSettings, + public CipherDetailsResponseModel( + CipherDetails cipher, + User user, + IDictionary organizationAbilities, + GlobalSettings globalSettings, IEnumerable collectionCiphers, string obj = "cipherDetails") - : base(cipher, globalSettings, obj) + : base(cipher, user, organizationAbilities, globalSettings, obj) { CollectionIds = collectionCiphers?.Select(c => c.CollectionId) ?? new List(); } - public CipherDetailsResponseModel(CipherDetailsWithCollections cipher, GlobalSettings globalSettings, string obj = "cipherDetails") - : base(cipher, globalSettings, obj) + public CipherDetailsResponseModel( + CipherDetailsWithCollections cipher, + User user, + IDictionary organizationAbilities, + GlobalSettings globalSettings, + string obj = "cipherDetails") + : base(cipher, user, organizationAbilities, globalSettings, obj) { CollectionIds = cipher.CollectionIds ?? new List(); } diff --git a/src/Api/Vault/Models/Response/SyncResponseModel.cs b/src/Api/Vault/Models/Response/SyncResponseModel.cs index a9b87ac31e..f1465264f2 100644 --- a/src/Api/Vault/Models/Response/SyncResponseModel.cs +++ b/src/Api/Vault/Models/Response/SyncResponseModel.cs @@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.Entities; using Bit.Core.Models.Api; using Bit.Core.Models.Data; +using Bit.Core.Models.Data.Organizations; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Settings; using Bit.Core.Tools.Entities; @@ -21,6 +22,7 @@ public class SyncResponseModel : ResponseModel User user, bool userTwoFactorEnabled, bool userHasPremiumFromOrganization, + IDictionary organizationAbilities, IEnumerable organizationIdsManagingUser, IEnumerable organizationUserDetails, IEnumerable providerUserDetails, @@ -37,7 +39,13 @@ public class SyncResponseModel : ResponseModel Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsManagingUser); Folders = folders.Select(f => new FolderResponseModel(f)); - Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict)); + Ciphers = ciphers.Select(cipher => + new CipherDetailsResponseModel( + cipher, + user, + organizationAbilities, + globalSettings, + collectionCiphersDict)); Collections = collections?.Select( c => new CollectionDetailsResponseModel(c)) ?? new List(); Domains = excludeDomains ? null : new DomainsResponseModel(user, false); diff --git a/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs b/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs index 2afce14ac5..5c8de51062 100644 --- a/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs @@ -27,17 +27,18 @@ namespace Bit.Api.Test.Controllers; public class CiphersControllerTests { [Theory, BitAutoData] - public async Task PutPartialShouldReturnCipherWithGivenFolderAndFavoriteValues(Guid userId, Guid folderId, SutProvider sutProvider) + public async Task PutPartialShouldReturnCipherWithGivenFolderAndFavoriteValues(User user, Guid folderId, SutProvider sutProvider) { var isFavorite = true; var cipherId = Guid.NewGuid(); sutProvider.GetDependency() - .GetProperUserId(Arg.Any()) - .Returns(userId); + .GetUserByPrincipalAsync(Arg.Any()) + .Returns(user); var cipherDetails = new CipherDetails { + UserId = user.Id, Favorite = isFavorite, FolderId = folderId, Type = Core.Vault.Enums.CipherType.SecureNote, @@ -45,7 +46,7 @@ public class CiphersControllerTests }; sutProvider.GetDependency() - .GetByIdAsync(cipherId, userId) + .GetByIdAsync(cipherId, user.Id) .Returns(Task.FromResult(cipherDetails)); var result = await sutProvider.Sut.PutPartial(cipherId, new CipherPartialRequestModel { Favorite = isFavorite, FolderId = folderId.ToString() }); @@ -55,12 +56,12 @@ public class CiphersControllerTests } [Theory, BitAutoData] - public async Task PutCollections_vNextShouldThrowExceptionWhenCipherIsNullOrNoOrgValue(Guid id, CipherCollectionsRequestModel model, Guid userId, + public async Task PutCollections_vNextShouldThrowExceptionWhenCipherIsNullOrNoOrgValue(Guid id, CipherCollectionsRequestModel model, User user, SutProvider sutProvider) { - sutProvider.GetDependency().GetProperUserId(default).Returns(userId); + sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); sutProvider.GetDependency().OrganizationUser(Guid.NewGuid()).Returns(false); - sutProvider.GetDependency().GetByIdAsync(id, userId).ReturnsNull(); + sutProvider.GetDependency().GetByIdAsync(id, user.Id).ReturnsNull(); var requestAction = async () => await sutProvider.Sut.PutCollections_vNext(id, model); @@ -75,6 +76,7 @@ public class CiphersControllerTests sutProvider.GetDependency().GetByIdAsync(id, userId).ReturnsForAnyArgs(cipherDetails); sutProvider.GetDependency().GetManyByUserIdCipherIdAsync(userId, id).Returns((ICollection)new List()); + sutProvider.GetDependency().GetOrganizationAbilitiesAsync().Returns(new Dictionary { { cipherDetails.OrganizationId.Value, new OrganizationAbility() } }); var cipherService = sutProvider.GetDependency(); await sutProvider.Sut.PutCollections_vNext(id, model); @@ -90,6 +92,7 @@ public class CiphersControllerTests sutProvider.GetDependency().GetByIdAsync(id, userId).ReturnsForAnyArgs(cipherDetails); sutProvider.GetDependency().GetManyByUserIdCipherIdAsync(userId, id).Returns((ICollection)new List()); + sutProvider.GetDependency().GetOrganizationAbilitiesAsync().Returns(new Dictionary { { cipherDetails.OrganizationId.Value, new OrganizationAbility() } }); var result = await sutProvider.Sut.PutCollections_vNext(id, model); @@ -115,6 +118,7 @@ public class CiphersControllerTests private void SetupUserAndOrgMocks(Guid id, Guid userId, SutProvider sutProvider) { sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(new User { Id = userId }); sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); sutProvider.GetDependency().GetManyByUserIdCipherIdAsync(userId, id).Returns(new List()); }