From d2c6e2bfa4fae8a1a5512cace54ee7196fe20591 Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Wed, 23 Oct 2024 11:59:15 -0500 Subject: [PATCH 01/10] Adding CanToggleState to PoliciesControllers (api/public) endpoints. Added mappings wrapped in feature flag. --- .../Controllers/PoliciesController.cs | 56 +++++++++++++- .../Public/Controllers/PoliciesController.cs | 74 ++++++++++++++++--- .../Models/Response/PolicyResponseModel.cs | 11 +++ .../Api/Response/PolicyResponseModel.cs | 10 +++ .../VerifyOrganizationDomainCommand.cs | 3 - .../Implementations/SavePolicyCommand.cs | 3 +- .../SingleOrgPolicyValidator.cs | 24 +++++- 7 files changed, 161 insertions(+), 20 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 7bfd13c4088e..19ff021a6e14 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -1,7 +1,11 @@ -using Bit.Api.AdminConsole.Models.Request; +using System.Collections.Immutable; +using Bit.Api.AdminConsole.Models.Request; using Bit.Api.Models.Response; +using Bit.Core; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Api.Response; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Models.Business.Tokenables; @@ -31,6 +35,8 @@ public class PoliciesController : Controller private readonly GlobalSettings _globalSettings; private readonly IDataProtector _organizationServiceDataProtector; private readonly IDataProtectorTokenFactory _orgUserInviteTokenDataFactory; + private readonly IFeatureService _featureService; + private readonly IReadOnlyDictionary _policyValidators; public PoliciesController( IPolicyRepository policyRepository, @@ -40,7 +46,9 @@ public PoliciesController( ICurrentContext currentContext, GlobalSettings globalSettings, IDataProtectionProvider dataProtectionProvider, - IDataProtectorTokenFactory orgUserInviteTokenDataFactory) + IDataProtectorTokenFactory orgUserInviteTokenDataFactory, + IFeatureService featureService, + IEnumerable validators) { _policyRepository = policyRepository; _policyService = policyService; @@ -52,6 +60,9 @@ public PoliciesController( "OrganizationServiceDataProtector"); _orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory; + _featureService = featureService; + + _policyValidators = validators.ToImmutableDictionary(x => x.Type); } [HttpGet("{type}")] @@ -68,6 +79,22 @@ public async Task Get(string orgId, int type) throw new NotFoundException(); } + if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) + { + var canToggle = _policyValidators.ContainsKey(policy.Type) && string.IsNullOrWhiteSpace( + await _policyValidators[policy.Type] + .ValidateAsync( + new PolicyUpdate + { + Data = policy.Data, + Enabled = !policy.Enabled, + OrganizationId = policy.OrganizationId, + Type = policy.Type + }, policy)); + + return new PolicyResponseModel(policy, canToggle); + } + return new PolicyResponseModel(policy); } @@ -81,7 +108,30 @@ public async Task> Get(string orgId) } var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid); - var responses = policies.Select(p => new PolicyResponseModel(p)); + + if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) + { + return new ListResponseModel(policies.Select(p => new PolicyResponseModel(p))); + } + + var responses = new List(); + + foreach (var policy in policies) + { + var canToggle = _policyValidators.ContainsKey(policy.Type) && string.IsNullOrWhiteSpace( + await _policyValidators[policy.Type] + .ValidateAsync( + new PolicyUpdate + { + Data = policy.Data, + Enabled = !policy.Enabled, + OrganizationId = policy.OrganizationId, + Type = policy.Type + }, policy)); + + responses.Add(new PolicyResponseModel(policy, canToggle)); + } + return new ListResponseModel(responses); } diff --git a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs index 71e03a547ec7..8d2a1e9833c0 100644 --- a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs @@ -1,11 +1,16 @@ -using System.Net; +using System.Collections.Immutable; +using System.Net; using Bit.Api.AdminConsole.Public.Models.Request; using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; +using Bit.Core; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Context; +using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -18,15 +23,21 @@ public class PoliciesController : Controller private readonly IPolicyRepository _policyRepository; private readonly IPolicyService _policyService; private readonly ICurrentContext _currentContext; + private readonly IFeatureService _featureService; + private readonly IDictionary _policyValidators; public PoliciesController( IPolicyRepository policyRepository, IPolicyService policyService, - ICurrentContext currentContext) + ICurrentContext currentContext, + IFeatureService featureService, + IEnumerable policyValidators) { _policyRepository = policyRepository; _policyService = policyService; _currentContext = currentContext; + _featureService = featureService; + _policyValidators = policyValidators.ToImmutableDictionary(x => x.Type); } /// @@ -41,14 +52,32 @@ public PoliciesController( [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task Get(PolicyType type) { - var policy = await _policyRepository.GetByOrganizationIdTypeAsync( - _currentContext.OrganizationId.Value, type); + var policy = await _policyRepository.GetByOrganizationIdTypeAsync(_currentContext.OrganizationId.Value, type); if (policy == null) { return new NotFoundResult(); } - var response = new PolicyResponseModel(policy); - return new JsonResult(response); + + if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) + { + if (policy.Type == PolicyType.SingleOrg) + { + var canToggle = _policyValidators.ContainsKey(policy.Type) && string.IsNullOrWhiteSpace( + await _policyValidators[policy.Type] + .ValidateAsync( + new PolicyUpdate + { + Data = policy.Data, + Enabled = !policy.Enabled, + OrganizationId = policy.OrganizationId, + Type = policy.Type + }, policy)); + + return new JsonResult(new PolicyResponseModel(policy, canToggle)); + } + } + + return new JsonResult(new PolicyResponseModel(policy)); } /// @@ -62,9 +91,36 @@ public async Task Get(PolicyType type) public async Task List() { var policies = await _policyRepository.GetManyByOrganizationIdAsync(_currentContext.OrganizationId.Value); - var policyResponses = policies.Select(p => new PolicyResponseModel(p)); - var response = new ListResponseModel(policyResponses); - return new JsonResult(response); + + if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) + { + return new JsonResult(new ListResponseModel(policies.Select(p => new PolicyResponseModel(p)))); + } + + var responses = new List(); + + foreach (var policy in policies) + { + if (policy.Type == PolicyType.SingleOrg) + { + var canToggle = _policyValidators.ContainsKey(policy.Type) && string.IsNullOrWhiteSpace( + await _policyValidators[policy.Type] + .ValidateAsync( + new PolicyUpdate + { + Data = policy.Data, + Enabled = !policy.Enabled, + OrganizationId = policy.OrganizationId, + Type = policy.Type + }, policy)); + + responses.Add(new PolicyResponseModel(policy, canToggle)); + } + + responses.Add(new PolicyResponseModel(policy)); + } + + return new JsonResult(new ListResponseModel(responses)); } /// diff --git a/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs index 27da5cc56120..43fe20f6987b 100644 --- a/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs +++ b/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs @@ -27,6 +27,11 @@ public PolicyResponseModel(Policy policy) } } + public PolicyResponseModel(Policy policy, bool canToggleState) : this(policy) + { + CanToggleState = canToggleState; + } + /// /// String representing the object's type. Objects of the same type share the same properties. /// @@ -44,4 +49,10 @@ public PolicyResponseModel(Policy policy) /// [Required] public PolicyType? Type { get; set; } + + /// + /// Indicates whether the Policy can be enabled/disabled + /// + [Required] + public bool CanToggleState { get; set; } } diff --git a/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs b/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs index 7ef6b15737fd..a27ba16a9ac9 100644 --- a/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs +++ b/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs @@ -25,9 +25,19 @@ public PolicyResponseModel(Policy policy, string obj = "policy") } } + public PolicyResponseModel(Policy policy, bool canToggleState) : this(policy) + { + CanToggleState = canToggleState; + } + public Guid Id { get; set; } public Guid OrganizationId { get; set; } public PolicyType Type { get; set; } public Dictionary Data { get; set; } public bool Enabled { get; set; } + + /// + /// Indicates whether the Policy can be enabled/disabled + /// + public bool CanToggleState { get; set; } = true; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs index 4a597a290c86..870fa72aa709 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs @@ -20,7 +20,6 @@ public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand private readonly IGlobalSettings _globalSettings; private readonly IPolicyService _policyService; private readonly IFeatureService _featureService; - private readonly IOrganizationService _organizationService; private readonly ILogger _logger; public VerifyOrganizationDomainCommand( @@ -30,7 +29,6 @@ public VerifyOrganizationDomainCommand( IGlobalSettings globalSettings, IPolicyService policyService, IFeatureService featureService, - IOrganizationService organizationService, ILogger logger) { _organizationDomainRepository = organizationDomainRepository; @@ -39,7 +37,6 @@ public VerifyOrganizationDomainCommand( _globalSettings = globalSettings; _policyService = policyService; _featureService = featureService; - _organizationService = organizationService; _logger = logger; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SavePolicyCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SavePolicyCommand.cs index 01ffce2cc670..f193aeabd1f6 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SavePolicyCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SavePolicyCommand.cs @@ -87,8 +87,7 @@ private async Task RunValidatorAsync(IPolicyValidator validator, PolicyUpdate po if (currentPolicy is not { Enabled: true } && policyUpdate.Enabled) { var missingRequiredPolicyTypes = validator.RequiredPolicies - .Where(requiredPolicyType => - savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true }) + .Where(requiredPolicyType => savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true }) .ToList(); if (missingRequiredPolicyTypes.Count != 0) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs index 3e1f8d26c81d..741c9245c108 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs @@ -1,7 +1,9 @@ #nullable enable +using System.Text; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.Auth.Enums; @@ -23,7 +25,9 @@ public class SingleOrgPolicyValidator : IPolicyValidator private readonly IOrganizationRepository _organizationRepository; private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ICurrentContext _currentContext; + private readonly IFeatureService _featureService; private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; + private readonly IOrganizationHasVerifiedDomainsQuery _organizationHasVerifiedDomainsQuery; public SingleOrgPolicyValidator( IOrganizationUserRepository organizationUserRepository, @@ -31,14 +35,18 @@ public SingleOrgPolicyValidator( IOrganizationRepository organizationRepository, ISsoConfigRepository ssoConfigRepository, ICurrentContext currentContext, - IRemoveOrganizationUserCommand removeOrganizationUserCommand) + IFeatureService featureService, + IRemoveOrganizationUserCommand removeOrganizationUserCommand, + IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery) { _organizationUserRepository = organizationUserRepository; _mailService = mailService; _organizationRepository = organizationRepository; _ssoConfigRepository = ssoConfigRepository; _currentContext = currentContext; + _featureService = featureService; _removeOrganizationUserCommand = removeOrganizationUserCommand; + _organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery; } public IEnumerable RequiredPolicies => []; @@ -92,10 +100,20 @@ public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? curre { if (policyUpdate is not { Enabled: true }) { + var resultString = new StringBuilder(); + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(policyUpdate.OrganizationId); - return ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector]); + resultString.Append(ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector])); + + if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) + && await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)) + { + resultString.Append("Organization has verified domains."); + } + + return resultString.ToString(); } - return ""; + return string.Empty; } } From 55d1d96b0ba2daab800cb8af3cc6c50d809239b0 Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Thu, 24 Oct 2024 14:22:58 -0500 Subject: [PATCH 02/10] Correcting error mesasge. --- src/Core/AdminConsole/Services/Implementations/PolicyService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/AdminConsole/Services/Implementations/PolicyService.cs b/src/Core/AdminConsole/Services/Implementations/PolicyService.cs index 072aa8283489..42655040a3c1 100644 --- a/src/Core/AdminConsole/Services/Implementations/PolicyService.cs +++ b/src/Core/AdminConsole/Services/Implementations/PolicyService.cs @@ -289,7 +289,7 @@ private async Task HasVerifiedDomainsAsync(Organization org) if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(org.Id)) { - throw new BadRequestException("Organization has verified domains."); + throw new BadRequestException("The Single organization policy is required for organizations that have enabled domain verification."); } } From af262c4849dd8b6930e8ff64ef6f9c2148202cb2 Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Thu, 24 Oct 2024 14:41:27 -0500 Subject: [PATCH 03/10] Corrected test and updated validator as well. --- .../Policies/PolicyValidators/SingleOrgPolicyValidator.cs | 2 +- test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs index 741c9245c108..fd40a4358711 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs @@ -108,7 +108,7 @@ public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? curre if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)) { - resultString.Append("Organization has verified domains."); + resultString.Append("The Single organization policy is required for organizations that have enabled domain verification."); } return resultString.ToString(); diff --git a/test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs b/test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs index da3f2b26779c..68f36e37ce83 100644 --- a/test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs @@ -842,6 +842,6 @@ public async Task SaveAsync_GivenOrganizationUsingPoliciesAndHasVerifiedDomains_ var badRequestException = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveAsync(policy, null)); - Assert.Equal("Organization has verified domains.", badRequestException.Message); + Assert.Equal("The Single organization policy is required for organizations that have enabled domain verification.", badRequestException.Message); } } From 6f5ce9d75c0d6726c3c55b0b3d1476cffc34dd9e Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Thu, 24 Oct 2024 14:50:28 -0500 Subject: [PATCH 04/10] Fixed tests that were failing due to AutoFixture trying to insert the same type of policy validator into its DI. --- .../AdminConsole/Controllers/PoliciesController.cs | 10 +++++++--- .../Public/Controllers/PoliciesController.cs | 13 +++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 19ff021a6e14..6f32e3520818 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -1,5 +1,4 @@ -using System.Collections.Immutable; -using Bit.Api.AdminConsole.Models.Request; +using Bit.Api.AdminConsole.Models.Request; using Bit.Api.Models.Response; using Bit.Core; using Bit.Core.AdminConsole.Enums; @@ -62,7 +61,12 @@ public PoliciesController( _orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory; _featureService = featureService; - _policyValidators = validators.ToImmutableDictionary(x => x.Type); + var dictionary = new Dictionary(); + foreach (var validator in validators) + { + dictionary.TryAdd(validator.Type, validator); + } + _policyValidators = dictionary; } [HttpGet("{type}")] diff --git a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs index 8d2a1e9833c0..3ad2544a41de 100644 --- a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs @@ -1,5 +1,4 @@ -using System.Collections.Immutable; -using System.Net; +using System.Net; using Bit.Api.AdminConsole.Public.Models.Request; using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; @@ -24,7 +23,7 @@ public class PoliciesController : Controller private readonly IPolicyService _policyService; private readonly ICurrentContext _currentContext; private readonly IFeatureService _featureService; - private readonly IDictionary _policyValidators; + private readonly IReadOnlyDictionary _policyValidators; public PoliciesController( IPolicyRepository policyRepository, @@ -37,7 +36,13 @@ public PoliciesController( _policyService = policyService; _currentContext = currentContext; _featureService = featureService; - _policyValidators = policyValidators.ToImmutableDictionary(x => x.Type); + + var dictionary = new Dictionary(); + foreach (var validator in policyValidators) + { + dictionary.TryAdd(validator.Type, validator); + } + _policyValidators = dictionary; } /// From dfa3f987014f8623a4361f2413ba85c18cc99188 Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Mon, 28 Oct 2024 10:30:31 -0500 Subject: [PATCH 05/10] Updated logic for determining CanToggle. Removed setting of toggle from List endpoint. Added new details model for single policy response. Validator now returns after first error. --- .../Controllers/PoliciesController.cs | 36 ++++--------------- .../Public/Controllers/PoliciesController.cs | 36 +++---------------- .../Response/PolicyDetailResponseModel.cs | 22 ++++++++++++ .../Models/Response/PolicyResponseModel.cs | 11 ------ .../Api/Response/PolicyDetailResponseModel.cs | 20 +++++++++++ .../Api/Response/PolicyResponseModel.cs | 10 ------ .../SingleOrgPolicyValidator.cs | 15 ++++---- 7 files changed, 61 insertions(+), 89 deletions(-) create mode 100644 src/Api/AdminConsole/Public/Models/Response/PolicyDetailResponseModel.cs create mode 100644 src/Core/AdminConsole/Models/Api/Response/PolicyDetailResponseModel.cs diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 6f32e3520818..67757cef4aed 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -70,7 +70,7 @@ public PoliciesController( } [HttpGet("{type}")] - public async Task Get(string orgId, int type) + public async Task Get(string orgId, int type) { var orgIdGuid = new Guid(orgId); if (!await _currentContext.ManagePolicies(orgIdGuid)) @@ -83,9 +83,10 @@ public async Task Get(string orgId, int type) throw new NotFoundException(); } - if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) + if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) + && policy.Type is PolicyType.SingleOrg) { - var canToggle = _policyValidators.ContainsKey(policy.Type) && string.IsNullOrWhiteSpace( + var canToggle = !_policyValidators.ContainsKey(policy.Type) || string.IsNullOrWhiteSpace( await _policyValidators[policy.Type] .ValidateAsync( new PolicyUpdate @@ -96,10 +97,10 @@ await _policyValidators[policy.Type] Type = policy.Type }, policy)); - return new PolicyResponseModel(policy, canToggle); + return new PolicyDetailResponseModel(policy, canToggle); } - return new PolicyResponseModel(policy); + return new PolicyDetailResponseModel(policy); } [HttpGet("")] @@ -113,30 +114,7 @@ public async Task> Get(string orgId) var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid); - if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) - { - return new ListResponseModel(policies.Select(p => new PolicyResponseModel(p))); - } - - var responses = new List(); - - foreach (var policy in policies) - { - var canToggle = _policyValidators.ContainsKey(policy.Type) && string.IsNullOrWhiteSpace( - await _policyValidators[policy.Type] - .ValidateAsync( - new PolicyUpdate - { - Data = policy.Data, - Enabled = !policy.Enabled, - OrganizationId = policy.OrganizationId, - Type = policy.Type - }, policy)); - - responses.Add(new PolicyResponseModel(policy, canToggle)); - } - - return new ListResponseModel(responses); + return new ListResponseModel(policies.Select(p => new PolicyResponseModel(p))); } [AllowAnonymous] diff --git a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs index 3ad2544a41de..fddf1916a2db 100644 --- a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs @@ -67,7 +67,7 @@ public async Task Get(PolicyType type) { if (policy.Type == PolicyType.SingleOrg) { - var canToggle = _policyValidators.ContainsKey(policy.Type) && string.IsNullOrWhiteSpace( + var canToggle = !_policyValidators.ContainsKey(policy.Type) || string.IsNullOrWhiteSpace( await _policyValidators[policy.Type] .ValidateAsync( new PolicyUpdate @@ -78,11 +78,11 @@ await _policyValidators[policy.Type] Type = policy.Type }, policy)); - return new JsonResult(new PolicyResponseModel(policy, canToggle)); + return new JsonResult(new PolicyDetailResponseModel(policy, canToggle)); } } - return new JsonResult(new PolicyResponseModel(policy)); + return new JsonResult(new PolicyDetailResponseModel(policy)); } /// @@ -97,35 +97,7 @@ public async Task List() { var policies = await _policyRepository.GetManyByOrganizationIdAsync(_currentContext.OrganizationId.Value); - if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) - { - return new JsonResult(new ListResponseModel(policies.Select(p => new PolicyResponseModel(p)))); - } - - var responses = new List(); - - foreach (var policy in policies) - { - if (policy.Type == PolicyType.SingleOrg) - { - var canToggle = _policyValidators.ContainsKey(policy.Type) && string.IsNullOrWhiteSpace( - await _policyValidators[policy.Type] - .ValidateAsync( - new PolicyUpdate - { - Data = policy.Data, - Enabled = !policy.Enabled, - OrganizationId = policy.OrganizationId, - Type = policy.Type - }, policy)); - - responses.Add(new PolicyResponseModel(policy, canToggle)); - } - - responses.Add(new PolicyResponseModel(policy)); - } - - return new JsonResult(new ListResponseModel(responses)); + return new JsonResult(new ListResponseModel(policies.Select(p => new PolicyResponseModel(p)))); } /// diff --git a/src/Api/AdminConsole/Public/Models/Response/PolicyDetailResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/PolicyDetailResponseModel.cs new file mode 100644 index 000000000000..ca8eee0090c4 --- /dev/null +++ b/src/Api/AdminConsole/Public/Models/Response/PolicyDetailResponseModel.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.AdminConsole.Entities; + +namespace Bit.Api.AdminConsole.Public.Models.Response; + +public class PolicyDetailResponseModel : PolicyResponseModel +{ + public PolicyDetailResponseModel(Policy policy) : base(policy) + { + } + + public PolicyDetailResponseModel(Policy policy, bool canToggleState) : this(policy) + { + CanToggleState = canToggleState; + } + + /// + /// Indicates whether the Policy can be enabled/disabled + /// + [Required] + public bool CanToggleState { get; set; } = true; +} diff --git a/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs index 43fe20f6987b..27da5cc56120 100644 --- a/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs +++ b/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs @@ -27,11 +27,6 @@ public PolicyResponseModel(Policy policy) } } - public PolicyResponseModel(Policy policy, bool canToggleState) : this(policy) - { - CanToggleState = canToggleState; - } - /// /// String representing the object's type. Objects of the same type share the same properties. /// @@ -49,10 +44,4 @@ public PolicyResponseModel(Policy policy, bool canToggleState) : this(policy) /// [Required] public PolicyType? Type { get; set; } - - /// - /// Indicates whether the Policy can be enabled/disabled - /// - [Required] - public bool CanToggleState { get; set; } } diff --git a/src/Core/AdminConsole/Models/Api/Response/PolicyDetailResponseModel.cs b/src/Core/AdminConsole/Models/Api/Response/PolicyDetailResponseModel.cs new file mode 100644 index 000000000000..dd440190006d --- /dev/null +++ b/src/Core/AdminConsole/Models/Api/Response/PolicyDetailResponseModel.cs @@ -0,0 +1,20 @@ +using Bit.Core.AdminConsole.Entities; + +namespace Bit.Core.AdminConsole.Models.Api.Response; + +public class PolicyDetailResponseModel : PolicyResponseModel +{ + public PolicyDetailResponseModel(Policy policy, string obj = "policy") : base(policy, obj) + { + } + + public PolicyDetailResponseModel(Policy policy, bool canToggleState) : base(policy) + { + CanToggleState = canToggleState; + } + + /// + /// Indicates whether the Policy can be enabled/disabled + /// + public bool CanToggleState { get; set; } = true; +} diff --git a/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs b/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs index a27ba16a9ac9..7ef6b15737fd 100644 --- a/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs +++ b/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs @@ -25,19 +25,9 @@ public PolicyResponseModel(Policy policy, string obj = "policy") } } - public PolicyResponseModel(Policy policy, bool canToggleState) : this(policy) - { - CanToggleState = canToggleState; - } - public Guid Id { get; set; } public Guid OrganizationId { get; set; } public PolicyType Type { get; set; } public Dictionary Data { get; set; } public bool Enabled { get; set; } - - /// - /// Indicates whether the Policy can be enabled/disabled - /// - public bool CanToggleState { get; set; } = true; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs index fd40a4358711..cc6971f9462a 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs @@ -1,6 +1,5 @@ #nullable enable -using System.Text; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; @@ -100,18 +99,20 @@ public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? curre { if (policyUpdate is not { Enabled: true }) { - var resultString = new StringBuilder(); - var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(policyUpdate.OrganizationId); - resultString.Append(ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector])); + + var validateDecryptionErrorMessage = ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector]); + + if (!string.IsNullOrWhiteSpace(validateDecryptionErrorMessage)) + { + return validateDecryptionErrorMessage; + } if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)) { - resultString.Append("The Single organization policy is required for organizations that have enabled domain verification."); + return "The Single organization policy is required for organizations that have enabled domain verification."; } - - return resultString.ToString(); } return string.Empty; From 65fe1e08982db8ffcb5750a1173f7ed054fc485f Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Wed, 30 Oct 2024 09:35:29 -0500 Subject: [PATCH 06/10] Removing CanToggle from public endpoint. --- .../Public/Controllers/PoliciesController.cs | 37 +------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs index fddf1916a2db..82ba3c1ea36b 100644 --- a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs @@ -2,14 +2,10 @@ using Bit.Api.AdminConsole.Public.Models.Request; using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; -using Bit.Core; using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Context; -using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -22,27 +18,15 @@ public class PoliciesController : Controller private readonly IPolicyRepository _policyRepository; private readonly IPolicyService _policyService; private readonly ICurrentContext _currentContext; - private readonly IFeatureService _featureService; - private readonly IReadOnlyDictionary _policyValidators; public PoliciesController( IPolicyRepository policyRepository, IPolicyService policyService, - ICurrentContext currentContext, - IFeatureService featureService, - IEnumerable policyValidators) + ICurrentContext currentContext) { _policyRepository = policyRepository; _policyService = policyService; _currentContext = currentContext; - _featureService = featureService; - - var dictionary = new Dictionary(); - foreach (var validator in policyValidators) - { - dictionary.TryAdd(validator.Type, validator); - } - _policyValidators = dictionary; } /// @@ -63,25 +47,6 @@ public async Task Get(PolicyType type) return new NotFoundResult(); } - if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) - { - if (policy.Type == PolicyType.SingleOrg) - { - var canToggle = !_policyValidators.ContainsKey(policy.Type) || string.IsNullOrWhiteSpace( - await _policyValidators[policy.Type] - .ValidateAsync( - new PolicyUpdate - { - Data = policy.Data, - Enabled = !policy.Enabled, - OrganizationId = policy.OrganizationId, - Type = policy.Type - }, policy)); - - return new JsonResult(new PolicyDetailResponseModel(policy, canToggle)); - } - } - return new JsonResult(new PolicyDetailResponseModel(policy)); } From 1b0e4d7ee159aa2446b8a94572937089d58fd3a7 Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Wed, 30 Oct 2024 09:37:58 -0500 Subject: [PATCH 07/10] Removed unneeded type --- .../Public/Controllers/PoliciesController.cs | 2 +- .../Response/PolicyDetailResponseModel.cs | 22 ------------------- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 src/Api/AdminConsole/Public/Models/Response/PolicyDetailResponseModel.cs diff --git a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs index 82ba3c1ea36b..f2e7c35d2466 100644 --- a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs @@ -47,7 +47,7 @@ public async Task Get(PolicyType type) return new NotFoundResult(); } - return new JsonResult(new PolicyDetailResponseModel(policy)); + return new JsonResult(new PolicyResponseModel(policy)); } /// diff --git a/src/Api/AdminConsole/Public/Models/Response/PolicyDetailResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/PolicyDetailResponseModel.cs deleted file mode 100644 index ca8eee0090c4..000000000000 --- a/src/Api/AdminConsole/Public/Models/Response/PolicyDetailResponseModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Bit.Core.AdminConsole.Entities; - -namespace Bit.Api.AdminConsole.Public.Models.Response; - -public class PolicyDetailResponseModel : PolicyResponseModel -{ - public PolicyDetailResponseModel(Policy policy) : base(policy) - { - } - - public PolicyDetailResponseModel(Policy policy, bool canToggleState) : this(policy) - { - CanToggleState = canToggleState; - } - - /// - /// Indicates whether the Policy can be enabled/disabled - /// - [Required] - public bool CanToggleState { get; set; } = true; -} From 82aac7564014d8707509956c69d94607fd73ca4a Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Wed, 30 Oct 2024 10:24:05 -0500 Subject: [PATCH 08/10] Swapped out using the validator for the query specficially. --- .../Controllers/PoliciesController.cs | 32 ++++--------------- .../Response/Helpers/PolicyDetailResponses.cs | 19 +++++++++++ 2 files changed, 26 insertions(+), 25 deletions(-) create mode 100644 src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 67757cef4aed..6e9aa83d8c7e 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -1,10 +1,10 @@ using Bit.Api.AdminConsole.Models.Request; +using Bit.Api.AdminConsole.Models.Response.Helpers; using Bit.Api.Models.Response; using Bit.Core; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Api.Response; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Models.Business.Tokenables; @@ -35,7 +35,7 @@ public class PoliciesController : Controller private readonly IDataProtector _organizationServiceDataProtector; private readonly IDataProtectorTokenFactory _orgUserInviteTokenDataFactory; private readonly IFeatureService _featureService; - private readonly IReadOnlyDictionary _policyValidators; + private readonly IOrganizationHasVerifiedDomainsQuery _organizationHasVerifiedDomainsQuery; public PoliciesController( IPolicyRepository policyRepository, @@ -47,7 +47,7 @@ public PoliciesController( IDataProtectionProvider dataProtectionProvider, IDataProtectorTokenFactory orgUserInviteTokenDataFactory, IFeatureService featureService, - IEnumerable validators) + IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery) { _policyRepository = policyRepository; _policyService = policyService; @@ -60,13 +60,7 @@ public PoliciesController( _orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory; _featureService = featureService; - - var dictionary = new Dictionary(); - foreach (var validator in validators) - { - dictionary.TryAdd(validator.Type, validator); - } - _policyValidators = dictionary; + _organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery; } [HttpGet("{type}")] @@ -83,21 +77,9 @@ public async Task Get(string orgId, int type) throw new NotFoundException(); } - if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) - && policy.Type is PolicyType.SingleOrg) + if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && policy.Type is PolicyType.SingleOrg) { - var canToggle = !_policyValidators.ContainsKey(policy.Type) || string.IsNullOrWhiteSpace( - await _policyValidators[policy.Type] - .ValidateAsync( - new PolicyUpdate - { - Data = policy.Data, - Enabled = !policy.Enabled, - OrganizationId = policy.OrganizationId, - Type = policy.Type - }, policy)); - - return new PolicyDetailResponseModel(policy, canToggle); + return await PolicyDetailResponses.GetSingleOrgPolicyDetailResponseAsync(policy, _organizationHasVerifiedDomainsQuery); } return new PolicyDetailResponseModel(policy); diff --git a/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs b/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs new file mode 100644 index 000000000000..0a26b0a8d970 --- /dev/null +++ b/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs @@ -0,0 +1,19 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Api.Response; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; + +namespace Bit.Api.AdminConsole.Models.Response.Helpers; + +public static class PolicyDetailResponses +{ + public static async Task GetSingleOrgPolicyDetailResponseAsync(Policy policy, IOrganizationHasVerifiedDomainsQuery hasVerifiedDomainsQuery) + { + if (policy.Type is not PolicyType.SingleOrg) + { + throw new ArgumentException($"'{nameof(policy)}' must be of type '{nameof(PolicyType.SingleOrg)}'.", nameof(policy)); + } + + return new PolicyDetailResponseModel(policy, await hasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policy.OrganizationId)); + } +} From 25030212ebdb039565ab10afd9285325819291ac Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Wed, 30 Oct 2024 10:55:40 -0500 Subject: [PATCH 09/10] clean up --- src/Api/AdminConsole/Controllers/PoliciesController.cs | 4 ++-- test/Api.Test/Controllers/PoliciesControllerTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index cd51dcb9cb39..83e4d5f9486f 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -2,6 +2,7 @@ using Bit.Api.AdminConsole.Models.Response.Helpers; using Bit.Api.Models.Response; using Bit.Core; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Api.Response; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; @@ -19,7 +20,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; -using AdminConsoleEntities = Bit.Core.AdminConsole.Entities; namespace Bit.Api.AdminConsole.Controllers; @@ -74,7 +74,7 @@ public async Task Get(Guid orgId, int type) var policy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type); if (policy == null) { - return new PolicyResponseModel(new AdminConsoleEntities.Policy() { Type = (PolicyType)type, Enabled = false }); + return new PolicyDetailResponseModel(new Policy { Type = (PolicyType)type }); } if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && policy.Type is PolicyType.SingleOrg) diff --git a/test/Api.Test/Controllers/PoliciesControllerTests.cs b/test/Api.Test/Controllers/PoliciesControllerTests.cs index 77cc5ea02c99..d88d9ab46a03 100644 --- a/test/Api.Test/Controllers/PoliciesControllerTests.cs +++ b/test/Api.Test/Controllers/PoliciesControllerTests.cs @@ -157,7 +157,7 @@ public async Task Get_WhenUserCanManagePolicies_WithExistingType_ReturnsExisting var result = await sutProvider.Sut.Get(orgId, type); // Assert - Assert.IsType(result); + Assert.IsType(result); Assert.Equal(policy.Id, result.Id); Assert.Equal(policy.Type, result.Type); Assert.Equal(policy.Enabled, result.Enabled); @@ -182,7 +182,7 @@ public async Task Get_WhenUserCanManagePolicies_WithNonExistingType_ReturnsDefau var result = await sutProvider.Sut.Get(orgId, type); // Assert - Assert.IsType(result); + Assert.IsType(result); Assert.Equal(result.Type, (PolicyType)type); Assert.False(result.Enabled); } From 1621b0baa855735f00a89d109246fb1d854c2043 Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Fri, 1 Nov 2024 10:34:21 -0500 Subject: [PATCH 10/10] Moved responses to Api project. Added tests around PolicyDetailResponses method. --- .../Controllers/PoliciesController.cs | 4 +- .../Response/Helpers/PolicyDetailResponses.cs | 8 +-- .../PolicyDetailResponseModel.cs | 2 +- .../Organizations}/PolicyResponseModel.cs | 2 +- .../Controllers/EmergencyAccessController.cs | 2 +- .../Models/Response/SyncResponseModel.cs | 4 +- .../Helpers/PolicyDetailResponsesTests.cs | 69 +++++++++++++++++++ .../Controllers/PoliciesControllerTests.cs | 2 +- 8 files changed, 81 insertions(+), 12 deletions(-) rename src/{Core/AdminConsole/Models/Api/Response => Api/AdminConsole/Models/Response/Organizations}/PolicyDetailResponseModel.cs (89%) rename src/{Core/AdminConsole/Models/Api/Response => Api/AdminConsole/Models/Response/Organizations}/PolicyResponseModel.cs (93%) create mode 100644 test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyDetailResponsesTests.cs diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 83e4d5f9486f..ee48cdd5d470 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -1,10 +1,10 @@ using Bit.Api.AdminConsole.Models.Request; using Bit.Api.AdminConsole.Models.Response.Helpers; +using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.Models.Response; using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.Models.Api.Response; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; @@ -79,7 +79,7 @@ public async Task Get(Guid orgId, int type) if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && policy.Type is PolicyType.SingleOrg) { - return await PolicyDetailResponses.GetSingleOrgPolicyDetailResponseAsync(policy, _organizationHasVerifiedDomainsQuery); + return await policy.GetSingleOrgPolicyDetailResponseAsync(_organizationHasVerifiedDomainsQuery); } return new PolicyDetailResponseModel(policy); diff --git a/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs b/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs index 0a26b0a8d970..14b9642f618a 100644 --- a/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs +++ b/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs @@ -1,19 +1,19 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Api.AdminConsole.Models.Response.Organizations; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.Models.Api.Response; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; namespace Bit.Api.AdminConsole.Models.Response.Helpers; public static class PolicyDetailResponses { - public static async Task GetSingleOrgPolicyDetailResponseAsync(Policy policy, IOrganizationHasVerifiedDomainsQuery hasVerifiedDomainsQuery) + public static async Task GetSingleOrgPolicyDetailResponseAsync(this Policy policy, IOrganizationHasVerifiedDomainsQuery hasVerifiedDomainsQuery) { if (policy.Type is not PolicyType.SingleOrg) { throw new ArgumentException($"'{nameof(policy)}' must be of type '{nameof(PolicyType.SingleOrg)}'.", nameof(policy)); } - return new PolicyDetailResponseModel(policy, await hasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policy.OrganizationId)); + return new PolicyDetailResponseModel(policy, !await hasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policy.OrganizationId)); } } diff --git a/src/Core/AdminConsole/Models/Api/Response/PolicyDetailResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/PolicyDetailResponseModel.cs similarity index 89% rename from src/Core/AdminConsole/Models/Api/Response/PolicyDetailResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Organizations/PolicyDetailResponseModel.cs index dd440190006d..cb5560e689e0 100644 --- a/src/Core/AdminConsole/Models/Api/Response/PolicyDetailResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/PolicyDetailResponseModel.cs @@ -1,6 +1,6 @@ using Bit.Core.AdminConsole.Entities; -namespace Bit.Core.AdminConsole.Models.Api.Response; +namespace Bit.Api.AdminConsole.Models.Response.Organizations; public class PolicyDetailResponseModel : PolicyResponseModel { diff --git a/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs similarity index 93% rename from src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs index 7ef6b15737fd..86e62a4193ec 100644 --- a/src/Core/AdminConsole/Models/Api/Response/PolicyResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs @@ -3,7 +3,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.Models.Api; -namespace Bit.Core.AdminConsole.Models.Api.Response; +namespace Bit.Api.AdminConsole.Models.Response.Organizations; public class PolicyResponseModel : ResponseModel { diff --git a/src/Api/Auth/Controllers/EmergencyAccessController.cs b/src/Api/Auth/Controllers/EmergencyAccessController.cs index 95fac234c8a9..9f8ea3df01af 100644 --- a/src/Api/Auth/Controllers/EmergencyAccessController.cs +++ b/src/Api/Auth/Controllers/EmergencyAccessController.cs @@ -1,10 +1,10 @@ using Bit.Api.AdminConsole.Models.Request.Organizations; +using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.Auth.Models.Request; using Bit.Api.Auth.Models.Response; using Bit.Api.Models.Response; using Bit.Api.Vault.Models.Response; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Api.Response; using Bit.Core.Auth.Services; using Bit.Core.Exceptions; using Bit.Core.Repositories; diff --git a/src/Api/Vault/Models/Response/SyncResponseModel.cs b/src/Api/Vault/Models/Response/SyncResponseModel.cs index ce5f4562d8d8..a9b87ac31eb4 100644 --- a/src/Api/Vault/Models/Response/SyncResponseModel.cs +++ b/src/Api/Vault/Models/Response/SyncResponseModel.cs @@ -1,7 +1,7 @@ -using Bit.Api.Models.Response; +using Bit.Api.AdminConsole.Models.Response.Organizations; +using Bit.Api.Models.Response; using Bit.Api.Tools.Models.Response; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Api.Response; using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.Entities; using Bit.Core.Models.Api; diff --git a/test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyDetailResponsesTests.cs b/test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyDetailResponsesTests.cs new file mode 100644 index 000000000000..c380185a70c4 --- /dev/null +++ b/test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyDetailResponsesTests.cs @@ -0,0 +1,69 @@ +using AutoFixture; +using Bit.Api.AdminConsole.Models.Response.Helpers; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.AdminConsole.Models.Response.Helpers; + +public class PolicyDetailResponsesTests +{ + [Fact] + public async Task GetSingleOrgPolicyDetailResponseAsync_GivenPolicyEntity_WhenIsSingleOrgTypeAndHasVerifiedDomains_ThenShouldNotBeAbleToToggle() + { + var fixture = new Fixture(); + + var policy = fixture.Build() + .Without(p => p.Data) + .With(p => p.Type, PolicyType.SingleOrg) + .Create(); + + var querySub = Substitute.For(); + querySub.HasVerifiedDomainsAsync(policy.OrganizationId) + .Returns(true); + + var result = await policy.GetSingleOrgPolicyDetailResponseAsync(querySub); + + Assert.False(result.CanToggleState); + } + + [Fact] + public async Task GetSingleOrgPolicyDetailResponseAsync_GivenPolicyEntity_WhenIsNotSingleOrgType_ThenShouldThrowArgumentException() + { + var fixture = new Fixture(); + + var policy = fixture.Build() + .Without(p => p.Data) + .With(p => p.Type, PolicyType.TwoFactorAuthentication) + .Create(); + + var querySub = Substitute.For(); + querySub.HasVerifiedDomainsAsync(policy.OrganizationId) + .Returns(true); + + var action = async () => await policy.GetSingleOrgPolicyDetailResponseAsync(querySub); + + await Assert.ThrowsAsync("policy", action); + } + + [Fact] + public async Task GetSingleOrgPolicyDetailResponseAsync_GivenPolicyEntity_WhenIsSingleOrgTypeAndDoesNotHaveVerifiedDomains_ThenShouldBeAbleToToggle() + { + var fixture = new Fixture(); + + var policy = fixture.Build() + .Without(p => p.Data) + .With(p => p.Type, PolicyType.SingleOrg) + .Create(); + + var querySub = Substitute.For(); + querySub.HasVerifiedDomainsAsync(policy.OrganizationId) + .Returns(false); + + var result = await policy.GetSingleOrgPolicyDetailResponseAsync(querySub); + + Assert.True(result.CanToggleState); + } +} diff --git a/test/Api.Test/Controllers/PoliciesControllerTests.cs b/test/Api.Test/Controllers/PoliciesControllerTests.cs index d88d9ab46a03..1b96ace5d0cb 100644 --- a/test/Api.Test/Controllers/PoliciesControllerTests.cs +++ b/test/Api.Test/Controllers/PoliciesControllerTests.cs @@ -1,9 +1,9 @@ using System.Security.Claims; using System.Text.Json; using Bit.Api.AdminConsole.Controllers; +using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.Models.Api.Response; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context;