diff --git a/src/Api/Vault/Controllers/SyncController.cs b/src/Api/Vault/Controllers/SyncController.cs index 1112ddfa2e..13eddbc98e 100644 --- a/src/Api/Vault/Controllers/SyncController.cs +++ b/src/Api/Vault/Controllers/SyncController.cs @@ -16,6 +16,7 @@ using Bit.Core.KeyManagement.Models.Data; using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; @@ -135,9 +136,18 @@ public class SyncController : Controller userAccountKeys = await _userAccountKeysQuery.Run(user); } + IEnumerable policiesNew = null; + IEnumerable organizationUserDetailsNew = null; + if (_featureService.IsEnabled(FeatureFlagKeys.PoliciesInAcceptedState)) + { + policiesNew = await _policyRepository.GetManyConfirmedAcceptedByUserIdAsync(user.Id); + organizationUserDetailsNew = await _organizationUserRepository.GetManyConfirmedAcceptedDetailsByUserAsync(user.Id); + } + var response = new SyncResponseModel(_globalSettings, user, userAccountKeys, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationAbilities, organizationIdsClaimingActiveUser, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails, - folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends, webAuthnCredentials); + folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends, webAuthnCredentials, + policiesNew, organizationUserDetailsNew); return response; } diff --git a/src/Api/Vault/Models/Response/SyncResponseModel.cs b/src/Api/Vault/Models/Response/SyncResponseModel.cs index fc363e1358..61b4dd7ab5 100644 --- a/src/Api/Vault/Models/Response/SyncResponseModel.cs +++ b/src/Api/Vault/Models/Response/SyncResponseModel.cs @@ -44,7 +44,9 @@ public class SyncResponseModel() : ResponseModel("sync") bool excludeDomains, IEnumerable policies, IEnumerable sends, - IEnumerable webAuthnCredentials) + IEnumerable webAuthnCredentials, + IEnumerable policiesNew = null, + IEnumerable organizationUserDetailsNew = null) : this() { Profile = new ProfileResponseModel(user, userAccountKeysData, organizationUserDetails, providerUserDetails, @@ -61,6 +63,8 @@ public class SyncResponseModel() : ResponseModel("sync") c => new CollectionDetailsResponseModel(c)) ?? new List(); Domains = excludeDomains ? null : new DomainsResponseModel(user, false); Policies = policies?.Select(p => new PolicyResponseModel(p)) ?? new List(); + PoliciesNew = policiesNew?.Select(p => new PolicyResponseModel(p)); + OrganizationsNew = organizationUserDetailsNew?.Select(o => new ProfileOrganizationResponseModel(o, organizationIdsClaimingingUser)); Sends = sends.Select(s => new SendResponseModel(s)); var webAuthnPrfOptions = webAuthnCredentials .Where(c => c.GetPrfStatus() == WebAuthnPrfStatus.Enabled) @@ -119,6 +123,18 @@ public class SyncResponseModel() : ResponseModel("sync") public IEnumerable Ciphers { get; set; } public DomainsResponseModel Domains { get; set; } public IEnumerable Policies { get; set; } + /// + /// Policies for organizations where the user is in the Confirmed or Accepted status. + /// Null when the pm-34145-policies-in-accepted-state feature flag is disabled. + /// New clients should prefer this property and fall back to if absent. + /// + public IEnumerable PoliciesNew { get; set; } + /// + /// Organizations where the user is in the Confirmed or Accepted status. + /// Null when the pm-34145-policies-in-accepted-state feature flag is disabled. + /// New clients should prefer this property and fall back to .Organizations if absent. + /// + public IEnumerable OrganizationsNew { get; set; } public IEnumerable Sends { get; set; } public UserDecryptionResponseModel UserDecryption { get; set; } } diff --git a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs index 97212e3f2e..151f8421c7 100644 --- a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs @@ -615,6 +615,86 @@ public class SyncControllerTests Assert.Contains(result.Ciphers, c => c.Type == CipherType.Login); } + [Theory] + [BitAutoData] + public async Task Get_PoliciesInAcceptedState_FlagEnabled_CallsNewRepositoryMethods( + User user, + ICollection policiesAccepted, + ICollection organizationsAccepted, + SutProvider sutProvider) + { + user.EquivalentDomains = null; + user.ExcludedGlobalEquivalentDomains = null; + + var userService = sutProvider.GetDependency(); + userService.GetUserByPrincipalAsync(Arg.Any()).ReturnsForAnyArgs(user); + + var userAccountKeysQuery = sutProvider.GetDependency(); + userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + SignatureKeyPairData = null, + }); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.PoliciesInAcceptedState).Returns(true); + + var policyRepository = sutProvider.GetDependency(); + policyRepository.GetManyConfirmedAcceptedByUserIdAsync(user.Id).Returns(policiesAccepted); + + var organizationUserRepository = sutProvider.GetDependency(); + organizationUserRepository.GetManyConfirmedAcceptedDetailsByUserAsync(user.Id).Returns(organizationsAccepted); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(user).Returns(false); + userService.HasPremiumFromOrganization(user).Returns(false); + + var result = await sutProvider.Sut.Get(); + + Assert.IsType(result); + await policyRepository.Received(1).GetManyConfirmedAcceptedByUserIdAsync(user.Id); + await organizationUserRepository.Received(1).GetManyConfirmedAcceptedDetailsByUserAsync(user.Id); + Assert.NotNull(result.PoliciesNew); + Assert.NotNull(result.OrganizationsNew); + } + + [Theory] + [BitAutoData] + public async Task Get_PoliciesInAcceptedState_FlagDisabled_DoesNotCallNewRepositoryMethods( + User user, + SutProvider sutProvider) + { + user.EquivalentDomains = null; + user.ExcludedGlobalEquivalentDomains = null; + + var userService = sutProvider.GetDependency(); + userService.GetUserByPrincipalAsync(Arg.Any()).ReturnsForAnyArgs(user); + + var userAccountKeysQuery = sutProvider.GetDependency(); + userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + SignatureKeyPairData = null, + }); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.PoliciesInAcceptedState).Returns(false); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(user).Returns(false); + userService.HasPremiumFromOrganization(user).Returns(false); + + var result = await sutProvider.Sut.Get(); + + Assert.IsType(result); + var policyRepository = sutProvider.GetDependency(); + var organizationUserRepository = sutProvider.GetDependency(); + await policyRepository.DidNotReceive().GetManyConfirmedAcceptedByUserIdAsync(Arg.Any()); + await organizationUserRepository.DidNotReceive().GetManyConfirmedAcceptedDetailsByUserAsync(Arg.Any()); + Assert.Null(result.PoliciesNew); + Assert.Null(result.OrganizationsNew); + } + private async Task AssertMethodsCalledAsync(IUserService userService, ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, IOrganizationUserRepository organizationUserRepository,