From 0544ec41d50f93fc858ce9d460ceb5cc71e8badc Mon Sep 17 00:00:00 2001
From: Alex Dragovich <46065570+itsadrago@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:48:12 -0800
Subject: [PATCH 1/2] [PM-31394] use email address hash for send access email
verification (#6921)
* [PM-31394] use email address hash for send access email verification
* [PM-31394] fixing identity server tests for send access
* [PM-31394] fixing more identity server tests for send access
---
.../Models/Data/SendAuthenticationTypes.cs | 4 ++--
.../SendAccess/SendEmailOtpRequestValidator.cs | 8 ++++++--
test/Common/Helpers/CryptographyHelper.cs | 17 +++++++++++++++++
.../Services/SendAuthenticationQueryTests.cs | 2 +-
...ndEmailOtpReqestValidatorIntegrationTests.cs | 10 ++++++----
.../SendEmailOtpRequestValidatorTests.cs | 13 +++++++++----
6 files changed, 41 insertions(+), 13 deletions(-)
create mode 100644 test/Common/Helpers/CryptographyHelper.cs
diff --git a/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs b/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
index c90dba43a8..769e9df713 100644
--- a/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
+++ b/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
@@ -44,7 +44,7 @@ public record ResourcePassword(string Hash) : SendAuthenticationMethod;
///
/// Create a send claim by requesting a one time password (OTP) confirmation code.
///
-///
+///
/// The list of email address **hashes** permitted access to the send.
///
-public record EmailOtp(string[] Emails) : SendAuthenticationMethod;
+public record EmailOtp(string[] EmailHashes) : SendAuthenticationMethod;
diff --git a/src/Identity/IdentityServer/RequestValidators/SendAccess/SendEmailOtpRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/SendAccess/SendEmailOtpRequestValidator.cs
index 34a7a6f6e7..f20fdb6f07 100644
--- a/src/Identity/IdentityServer/RequestValidators/SendAccess/SendEmailOtpRequestValidator.cs
+++ b/src/Identity/IdentityServer/RequestValidators/SendAccess/SendEmailOtpRequestValidator.cs
@@ -1,4 +1,6 @@
using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text;
using Bit.Core;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.Identity.TokenProviders;
@@ -40,8 +42,10 @@ public class SendEmailOtpRequestValidator(
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailRequired);
}
- // email must be in the list of emails in the EmailOtp array
- if (!authMethod.Emails.Contains(email))
+ // email hash must be in the list of email hashes in the EmailOtp array
+ byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(email));
+ string hashEmailHex = Convert.ToHexString(hashBytes).ToUpperInvariant();
+ if (!authMethod.EmailHashes.Contains(hashEmailHex))
{
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailInvalid);
}
diff --git a/test/Common/Helpers/CryptographyHelper.cs b/test/Common/Helpers/CryptographyHelper.cs
new file mode 100644
index 0000000000..30dfb1a679
--- /dev/null
+++ b/test/Common/Helpers/CryptographyHelper.cs
@@ -0,0 +1,17 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Bit.Test.Common.Helpers;
+
+public class CryptographyHelper
+{
+ ///
+ /// Returns a hex-encoded, SHA256 hash for the given string
+ ///
+ public static string HashAndEncode(string text)
+ {
+ var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(text));
+ var hashEncoded = Convert.ToHexString(hashBytes).ToUpperInvariant();
+ return hashEncoded;
+ }
+}
diff --git a/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs b/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs
index 56b0f306cb..b4b1ecbc79 100644
--- a/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs
+++ b/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs
@@ -56,7 +56,7 @@ public class SendAuthenticationQueryTests
// Assert
var emailOtp = Assert.IsType(result);
- Assert.Equal(expectedEmailHashes, emailOtp.Emails);
+ Assert.Equal(expectedEmailHashes, emailOtp.EmailHashes);
}
[Fact]
diff --git a/test/Identity.IntegrationTest/RequestValidation/SendAccess/SendEmailOtpReqestValidatorIntegrationTests.cs b/test/Identity.IntegrationTest/RequestValidation/SendAccess/SendEmailOtpReqestValidatorIntegrationTests.cs
index 3c4657653b..1c740cd448 100644
--- a/test/Identity.IntegrationTest/RequestValidation/SendAccess/SendEmailOtpReqestValidatorIntegrationTests.cs
+++ b/test/Identity.IntegrationTest/RequestValidation/SendAccess/SendEmailOtpReqestValidatorIntegrationTests.cs
@@ -3,6 +3,7 @@ using Bit.Core.Services;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
using Bit.IntegrationTestCommon.Factories;
+using Bit.Test.Common.Helpers;
using Duende.IdentityModel;
using NSubstitute;
using Xunit;
@@ -60,7 +61,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
var sendAuthQuery = Substitute.For();
sendAuthQuery.GetAuthenticationMethod(sendId)
- .Returns(new EmailOtp([email]));
+ .Returns(new EmailOtp([CryptographyHelper.HashAndEncode(email)]));
services.AddSingleton(sendAuthQuery);
// Mock OTP token provider
@@ -75,6 +76,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
});
}).CreateClient();
+
var requestBody = SendAccessTestUtilities.CreateTokenRequestBody(sendId, email: email); // Email but no OTP
// Act
@@ -104,7 +106,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
var sendAuthQuery = Substitute.For();
sendAuthQuery.GetAuthenticationMethod(sendId)
- .Returns(new EmailOtp(new[] { email }));
+ .Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
services.AddSingleton(sendAuthQuery);
// Mock OTP token provider to validate successfully
@@ -148,7 +150,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
var sendAuthQuery = Substitute.For();
sendAuthQuery.GetAuthenticationMethod(sendId)
- .Returns(new EmailOtp(new[] { email }));
+ .Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
services.AddSingleton(sendAuthQuery);
// Mock OTP token provider to validate as false
@@ -190,7 +192,7 @@ public class SendEmailOtpRequestValidatorIntegrationTests(IdentityApplicationFac
var sendAuthQuery = Substitute.For();
sendAuthQuery.GetAuthenticationMethod(sendId)
- .Returns(new EmailOtp(new[] { email }));
+ .Returns(new EmailOtp(new[] { CryptographyHelper.HashAndEncode(email) }));
services.AddSingleton(sendAuthQuery);
// Mock OTP token provider to fail generation
diff --git a/test/Identity.Test/IdentityServer/SendAccess/SendEmailOtpRequestValidatorTests.cs b/test/Identity.Test/IdentityServer/SendAccess/SendEmailOtpRequestValidatorTests.cs
index 7fdfacf428..1815b9207d 100644
--- a/test/Identity.Test/IdentityServer/SendAccess/SendEmailOtpRequestValidatorTests.cs
+++ b/test/Identity.Test/IdentityServer/SendAccess/SendEmailOtpRequestValidatorTests.cs
@@ -5,6 +5,7 @@ using Bit.Core.Tools.Models.Data;
using Bit.Identity.IdentityServer.RequestValidators.SendAccess;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
+using Bit.Test.Common.Helpers;
using Duende.IdentityModel;
using Duende.IdentityServer.Validation;
using NSubstitute;
@@ -105,7 +106,8 @@ public class SendEmailOtpRequestValidatorTests
expectedUniqueId)
.Returns(generatedToken);
- emailOtp = emailOtp with { Emails = [email] };
+ var emailHash = CryptographyHelper.HashAndEncode(email);
+ emailOtp = emailOtp with { EmailHashes = [emailHash] };
// Act
var result = await sutProvider.Sut.ValidateRequestAsync(context, emailOtp, sendId);
@@ -144,7 +146,8 @@ public class SendEmailOtpRequestValidatorTests
Request = tokenRequest
};
- emailOtp = emailOtp with { Emails = [email] };
+ var emailHash = CryptographyHelper.HashAndEncode(email);
+ emailOtp = emailOtp with { EmailHashes = [emailHash] };
sutProvider.GetDependency>()
.GenerateTokenAsync(Arg.Any(), Arg.Any(), Arg.Any())
@@ -179,7 +182,8 @@ public class SendEmailOtpRequestValidatorTests
Request = tokenRequest
};
- emailOtp = emailOtp with { Emails = [email] };
+ var emailHash = CryptographyHelper.HashAndEncode(email);
+ emailOtp = emailOtp with { EmailHashes = [emailHash] };
var expectedUniqueId = string.Format(SendAccessConstants.OtpToken.TokenUniqueIdentifier, sendId, email);
@@ -231,7 +235,8 @@ public class SendEmailOtpRequestValidatorTests
Request = tokenRequest
};
- emailOtp = emailOtp with { Emails = [email] };
+ var emailHash = CryptographyHelper.HashAndEncode(email);
+ emailOtp = emailOtp with { EmailHashes = [emailHash] };
var expectedUniqueId = string.Format(SendAccessConstants.OtpToken.TokenUniqueIdentifier, sendId, email);
From 93a28eed408a9438cc92f5067b1065ff47e387f5 Mon Sep 17 00:00:00 2001
From: sven-bitwarden
Date: Thu, 29 Jan 2026 14:11:20 -0600
Subject: [PATCH 2/2] [PM-29246] Simplify Usage of Organization Policies
(#6837)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Initial implementation of new policy query
* Remove unused using
* Adjusts method name to better match repository method
* Correct namespace
* Initial refactor of policy loading
* Add xml doc, incorporate shim data model
* Updates usages to reflect new shim model
* Prune extranneous data from policy detail response model, format code
* Fix broken test, delete inapplicable test
* Adds test cases covering query
* Adjust codebase to use new PolicyQueryçˆ
* Format code
* Fix incorrect mock on test
* Fix formatting
* Adjust method name
* More naming adjustments
* Add PolicyData constructor, update test usages
* Rename PolicyData -> PolicyStatus
* Remove unused using
---
.../src/Sso/Controllers/AccountController.cs | 13 ++---
.../OrganizationUsersController.cs | 13 ++---
.../Controllers/OrganizationsController.cs | 11 ++--
.../Controllers/PoliciesController.cs | 18 +++---
...lResponses.cs => PolicyStatusResponses.cs} | 11 ++--
.../PolicyDetailResponseModel.cs | 20 -------
.../PolicyStatusResponseModel.cs | 33 +++++++++++
.../OrganizationSponsorshipsController.cs | 24 ++++----
.../Organizations/Policies/PolicyStatus.cs | 26 +++++++++
.../AdminRecoverAccountCommand.cs | 9 ++-
...icallyConfirmOrganizationUsersValidator.cs | 5 +-
.../SendOrganizationInvitesCommand.cs | 6 +-
.../Policies/IPolicyQuery.cs | 17 ++++++
.../Policies/Implementations/PolicyQuery.cs | 14 +++++
.../PolicyServiceCollectionExtensions.cs | 1 +
.../Implementations/OrganizationService.cs | 11 ++--
.../Implementations/SsoConfigService.cs | 16 +++---
.../Implementations/RegisterUserCommand.cs | 12 ++--
.../UpgradeOrganizationPlanCommand.cs | 9 ++-
.../Services/Implementations/UserService.cs | 11 ++--
.../OrganizationUsersControllerTests.cs | 43 +++++++--------
.../OrganizationsControllerTests.cs | 17 ++----
...Tests.cs => PolicyStatusResponsesTests.cs} | 35 +++---------
...OrganizationSponsorshipsControllerTests.cs | 19 ++++++-
.../Controllers/PoliciesControllerTests.cs | 44 ++++-----------
.../AutoFixture/PolicyFixtures.cs | 24 ++++++--
.../AdminRecoverAccountCommandTests.cs | 36 ++++++------
...yConfirmOrganizationUsersValidatorTests.cs | 52 +++++++++---------
.../SendOrganizationInvitesCommandTests.cs | 10 +++-
.../Auth/Services/SsoConfigServiceTests.cs | 52 +++++++++---------
.../Registration/RegisterUserCommandTests.cs | 46 ++++++++++------
.../UpgradeOrganizationPlanCommandTests.cs | 46 +++++++++++++++-
.../Policies/PolicyQueryTests.cs | 55 +++++++++++++++++++
33 files changed, 457 insertions(+), 302 deletions(-)
rename src/Api/AdminConsole/Models/Response/Helpers/{PolicyDetailResponses.cs => PolicyStatusResponses.cs} (66%)
delete mode 100644 src/Api/AdminConsole/Models/Response/Organizations/PolicyDetailResponseModel.cs
create mode 100644 src/Api/AdminConsole/Models/Response/Organizations/PolicyStatusResponseModel.cs
create mode 100644 src/Core/AdminConsole/Models/Data/Organizations/Policies/PolicyStatus.cs
create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyQuery.cs
create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs
rename test/Api.Test/AdminConsole/Models/Response/Helpers/{PolicyDetailResponsesTests.cs => PolicyStatusResponsesTests.cs} (62%)
create mode 100644 test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs
diff --git a/bitwarden_license/src/Sso/Controllers/AccountController.cs b/bitwarden_license/src/Sso/Controllers/AccountController.cs
index dde2ac7a46..3d998b6a75 100644
--- a/bitwarden_license/src/Sso/Controllers/AccountController.cs
+++ b/bitwarden_license/src/Sso/Controllers/AccountController.cs
@@ -2,7 +2,7 @@
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
-using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
@@ -45,7 +45,7 @@ public class AccountController : Controller
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoUserRepository _ssoUserRepository;
private readonly IUserRepository _userRepository;
- private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly IUserService _userService;
private readonly II18nService _i18nService;
private readonly UserManager _userManager;
@@ -67,7 +67,7 @@ public class AccountController : Controller
ISsoConfigRepository ssoConfigRepository,
ISsoUserRepository ssoUserRepository,
IUserRepository userRepository,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IUserService userService,
II18nService i18nService,
UserManager userManager,
@@ -88,7 +88,7 @@ public class AccountController : Controller
_userRepository = userRepository;
_ssoConfigRepository = ssoConfigRepository;
_ssoUserRepository = ssoUserRepository;
- _policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_userService = userService;
_i18nService = i18nService;
_userManager = userManager;
@@ -687,9 +687,8 @@ public class AccountController : Controller
await _registerUserCommand.RegisterSSOAutoProvisionedUserAsync(newUser, organization);
// If the organization has 2fa policy enabled, make sure to default jit user 2fa to email
- var twoFactorPolicy =
- await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.TwoFactorAuthentication);
- if (twoFactorPolicy != null && twoFactorPolicy.Enabled)
+ var twoFactorPolicy = await _policyQuery.RunAsync(organization.Id, PolicyType.TwoFactorAuthentication);
+ if (twoFactorPolicy.Enabled)
{
newUser.SetTwoFactorProviders(new Dictionary
{
diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs
index 024c54a48e..37b58bc252 100644
--- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs
+++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs
@@ -57,7 +57,7 @@ public class OrganizationUsersController : BaseAdminConsoleController
private readonly ICollectionRepository _collectionRepository;
private readonly IGroupRepository _groupRepository;
private readonly IUserService _userService;
- private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly ICurrentContext _currentContext;
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
@@ -90,7 +90,7 @@ public class OrganizationUsersController : BaseAdminConsoleController
ICollectionRepository collectionRepository,
IGroupRepository groupRepository,
IUserService userService,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
ICurrentContext currentContext,
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
@@ -123,7 +123,7 @@ public class OrganizationUsersController : BaseAdminConsoleController
_collectionRepository = collectionRepository;
_groupRepository = groupRepository;
_userService = userService;
- _policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_currentContext = currentContext;
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
@@ -350,10 +350,9 @@ public class OrganizationUsersController : BaseAdminConsoleController
return false;
}
- var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
- var useMasterPasswordPolicy = masterPasswordPolicy != null &&
- masterPasswordPolicy.Enabled &&
- masterPasswordPolicy.GetDataModel().AutoEnrollEnabled;
+ var masterPasswordPolicy = await _policyQuery.RunAsync(orgId, PolicyType.ResetPassword);
+ var useMasterPasswordPolicy = masterPasswordPolicy.Enabled &&
+ masterPasswordPolicy.GetDataModel().AutoEnrollEnabled;
return useMasterPasswordPolicy;
}
diff --git a/src/Api/AdminConsole/Controllers/OrganizationsController.cs b/src/Api/AdminConsole/Controllers/OrganizationsController.cs
index 100cd7caf6..a6de8c521f 100644
--- a/src/Api/AdminConsole/Controllers/OrganizationsController.cs
+++ b/src/Api/AdminConsole/Controllers/OrganizationsController.cs
@@ -48,7 +48,7 @@ public class OrganizationsController : Controller
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
- private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
@@ -74,7 +74,7 @@ public class OrganizationsController : Controller
public OrganizationsController(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IOrganizationService organizationService,
IUserService userService,
ICurrentContext currentContext,
@@ -99,7 +99,7 @@ public class OrganizationsController : Controller
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
- _policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_organizationService = organizationService;
_userService = userService;
_currentContext = currentContext;
@@ -183,15 +183,14 @@ public class OrganizationsController : Controller
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, resetPasswordPolicyRequirement.AutoEnrollEnabled(organization.Id));
}
- var resetPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
- if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled || resetPasswordPolicy.Data == null)
+ var resetPasswordPolicy = await _policyQuery.RunAsync(organization.Id, PolicyType.ResetPassword);
+ if (!resetPasswordPolicy.Enabled || resetPasswordPolicy.Data == null)
{
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, false);
}
var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase);
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, data?.AutoEnrollEnabled ?? false);
-
}
[HttpPost("")]
diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs
index bce0332d67..fe3600c3dd 100644
--- a/src/Api/AdminConsole/Controllers/PoliciesController.cs
+++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs
@@ -7,7 +7,6 @@ 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.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
@@ -43,6 +42,7 @@ public class PoliciesController : Controller
private readonly IUserService _userService;
private readonly ISavePolicyCommand _savePolicyCommand;
private readonly IVNextSavePolicyCommand _vNextSavePolicyCommand;
+ private readonly IPolicyQuery _policyQuery;
public PoliciesController(IPolicyRepository policyRepository,
IOrganizationUserRepository organizationUserRepository,
@@ -54,7 +54,8 @@ public class PoliciesController : Controller
IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery,
IOrganizationRepository organizationRepository,
ISavePolicyCommand savePolicyCommand,
- IVNextSavePolicyCommand vNextSavePolicyCommand)
+ IVNextSavePolicyCommand vNextSavePolicyCommand,
+ IPolicyQuery policyQuery)
{
_policyRepository = policyRepository;
_organizationUserRepository = organizationUserRepository;
@@ -68,27 +69,24 @@ public class PoliciesController : Controller
_organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery;
_savePolicyCommand = savePolicyCommand;
_vNextSavePolicyCommand = vNextSavePolicyCommand;
+ _policyQuery = policyQuery;
}
[HttpGet("{type}")]
- public async Task Get(Guid orgId, int type)
+ public async Task Get(Guid orgId, PolicyType type)
{
if (!await _currentContext.ManagePolicies(orgId))
{
throw new NotFoundException();
}
- var policy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type);
- if (policy == null)
- {
- return new PolicyDetailResponseModel(new Policy { Type = (PolicyType)type });
- }
+ var policy = await _policyQuery.RunAsync(orgId, type);
if (policy.Type is PolicyType.SingleOrg)
{
- return await policy.GetSingleOrgPolicyDetailResponseAsync(_organizationHasVerifiedDomainsQuery);
+ return await policy.GetSingleOrgPolicyStatusResponseAsync(_organizationHasVerifiedDomainsQuery);
}
- return new PolicyDetailResponseModel(policy);
+ return new PolicyStatusResponseModel(policy);
}
[HttpGet("")]
diff --git a/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs b/src/Api/AdminConsole/Models/Response/Helpers/PolicyStatusResponses.cs
similarity index 66%
rename from src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs
rename to src/Api/AdminConsole/Models/Response/Helpers/PolicyStatusResponses.cs
index dded6a4c89..da08cdef0f 100644
--- a/src/Api/AdminConsole/Models/Response/Helpers/PolicyDetailResponses.cs
+++ b/src/Api/AdminConsole/Models/Response/Helpers/PolicyStatusResponses.cs
@@ -1,19 +1,21 @@
using Bit.Api.AdminConsole.Models.Response.Organizations;
-using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
namespace Bit.Api.AdminConsole.Models.Response.Helpers;
-public static class PolicyDetailResponses
+public static class PolicyStatusResponses
{
- public static async Task GetSingleOrgPolicyDetailResponseAsync(this Policy policy, IOrganizationHasVerifiedDomainsQuery hasVerifiedDomainsQuery)
+ public static async Task GetSingleOrgPolicyStatusResponseAsync(
+ this PolicyStatus 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 CanToggleState());
+
+ return new PolicyStatusResponseModel(policy, await CanToggleState());
async Task CanToggleState()
{
@@ -25,5 +27,4 @@ public static class PolicyDetailResponses
return !policy.Enabled;
}
}
-
}
diff --git a/src/Api/AdminConsole/Models/Response/Organizations/PolicyDetailResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/PolicyDetailResponseModel.cs
deleted file mode 100644
index cb5560e689..0000000000
--- a/src/Api/AdminConsole/Models/Response/Organizations/PolicyDetailResponseModel.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Bit.Core.AdminConsole.Entities;
-
-namespace Bit.Api.AdminConsole.Models.Response.Organizations;
-
-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/Api/AdminConsole/Models/Response/Organizations/PolicyStatusResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/PolicyStatusResponseModel.cs
new file mode 100644
index 0000000000..8c93302a17
--- /dev/null
+++ b/src/Api/AdminConsole/Models/Response/Organizations/PolicyStatusResponseModel.cs
@@ -0,0 +1,33 @@
+using System.Text.Json;
+using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+using Bit.Core.Models.Api;
+
+namespace Bit.Api.AdminConsole.Models.Response.Organizations;
+
+public class PolicyStatusResponseModel : ResponseModel
+{
+ public PolicyStatusResponseModel(PolicyStatus policy, bool canToggleState = true) : base("policy")
+ {
+ OrganizationId = policy.OrganizationId;
+ Type = policy.Type;
+
+ if (!string.IsNullOrWhiteSpace(policy.Data))
+ {
+ Data = JsonSerializer.Deserialize>(policy.Data) ?? new();
+ }
+
+ Enabled = policy.Enabled;
+ CanToggleState = canToggleState;
+ }
+
+ public Guid OrganizationId { get; init; }
+ public PolicyType Type { get; init; }
+ public Dictionary Data { get; init; } = new();
+ public bool Enabled { get; init; }
+
+ ///
+ /// Indicates whether the Policy can be enabled/disabled
+ ///
+ public bool CanToggleState { get; init; }
+}
diff --git a/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs b/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs
index 7ca85d52a8..8a1467dfa2 100644
--- a/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs
+++ b/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs
@@ -6,7 +6,7 @@ using Bit.Api.Models.Response;
using Bit.Api.Models.Response.Organizations;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
-using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
@@ -38,7 +38,7 @@ public class OrganizationSponsorshipsController : Controller
private readonly ICloudSyncSponsorshipsCommand _syncSponsorshipsCommand;
private readonly ICurrentContext _currentContext;
private readonly IUserService _userService;
- private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly IFeatureService _featureService;
public OrganizationSponsorshipsController(
@@ -55,7 +55,7 @@ public class OrganizationSponsorshipsController : Controller
ICloudSyncSponsorshipsCommand syncSponsorshipsCommand,
IUserService userService,
ICurrentContext currentContext,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IFeatureService featureService)
{
_organizationSponsorshipRepository = organizationSponsorshipRepository;
@@ -71,7 +71,7 @@ public class OrganizationSponsorshipsController : Controller
_syncSponsorshipsCommand = syncSponsorshipsCommand;
_userService = userService;
_currentContext = currentContext;
- _policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_featureService = featureService;
}
@@ -81,10 +81,10 @@ public class OrganizationSponsorshipsController : Controller
public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipCreateRequestModel model)
{
var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgId);
- var freeFamiliesSponsorshipPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(sponsoringOrgId,
+ var freeFamiliesSponsorshipPolicy = await _policyQuery.RunAsync(sponsoringOrgId,
PolicyType.FreeFamiliesSponsorshipPolicy);
- if (freeFamiliesSponsorshipPolicy?.Enabled == true)
+ if (freeFamiliesSponsorshipPolicy.Enabled)
{
throw new BadRequestException("Free Bitwarden Families sponsorship has been disabled by your organization administrator.");
}
@@ -108,10 +108,10 @@ public class OrganizationSponsorshipsController : Controller
[SelfHosted(NotSelfHostedOnly = true)]
public async Task ResendSponsorshipOffer(Guid sponsoringOrgId, [FromQuery] string sponsoredFriendlyName)
{
- var freeFamiliesSponsorshipPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(sponsoringOrgId,
+ var freeFamiliesSponsorshipPolicy = await _policyQuery.RunAsync(sponsoringOrgId,
PolicyType.FreeFamiliesSponsorshipPolicy);
- if (freeFamiliesSponsorshipPolicy?.Enabled == true)
+ if (freeFamiliesSponsorshipPolicy.Enabled)
{
throw new BadRequestException("Free Bitwarden Families sponsorship has been disabled by your organization administrator.");
}
@@ -138,9 +138,9 @@ public class OrganizationSponsorshipsController : Controller
var (isValid, sponsorship) = await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email);
if (isValid && sponsorship.SponsoringOrganizationId.HasValue)
{
- var policy = await _policyRepository.GetByOrganizationIdTypeAsync(sponsorship.SponsoringOrganizationId.Value,
+ var policy = await _policyQuery.RunAsync(sponsorship.SponsoringOrganizationId.Value,
PolicyType.FreeFamiliesSponsorshipPolicy);
- isFreeFamilyPolicyEnabled = policy?.Enabled ?? false;
+ isFreeFamilyPolicyEnabled = policy.Enabled;
}
var response = PreValidateSponsorshipResponseModel.From(isValid, isFreeFamilyPolicyEnabled);
@@ -165,10 +165,10 @@ public class OrganizationSponsorshipsController : Controller
throw new BadRequestException("Can only redeem sponsorship for an organization you own.");
}
- var freeFamiliesSponsorshipPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(
+ var freeFamiliesSponsorshipPolicy = await _policyQuery.RunAsync(
model.SponsoredOrganizationId, PolicyType.FreeFamiliesSponsorshipPolicy);
- if (freeFamiliesSponsorshipPolicy?.Enabled == true)
+ if (freeFamiliesSponsorshipPolicy.Enabled)
{
throw new BadRequestException("Free Bitwarden Families sponsorship has been disabled by your organization administrator.");
}
diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/PolicyStatus.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/PolicyStatus.cs
new file mode 100644
index 0000000000..68c754f6ba
--- /dev/null
+++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/PolicyStatus.cs
@@ -0,0 +1,26 @@
+using Bit.Core.AdminConsole.Entities;
+using Bit.Core.AdminConsole.Enums;
+using Bit.Core.Utilities;
+
+namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+
+public class PolicyStatus
+{
+ public PolicyStatus(Guid organizationId, PolicyType policyType, Policy? policy = null)
+ {
+ OrganizationId = policy?.OrganizationId ?? organizationId;
+ Data = policy?.Data;
+ Type = policy?.Type ?? policyType;
+ Enabled = policy?.Enabled ?? false;
+ }
+
+ public Guid OrganizationId { get; set; }
+ public PolicyType Type { get; set; }
+ public bool Enabled { get; set; }
+ public string? Data { get; set; }
+
+ public T GetDataModel() where T : IPolicyDataModel, new()
+ {
+ return CoreHelpers.LoadClassFromJsonData(Data);
+ }
+}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/AccountRecovery/AdminRecoverAccountCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/AccountRecovery/AdminRecoverAccountCommand.cs
index 5783301a0b..bd30112945 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/AccountRecovery/AdminRecoverAccountCommand.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/AccountRecovery/AdminRecoverAccountCommand.cs
@@ -1,5 +1,5 @@
using Bit.Core.AdminConsole.Enums;
-using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Identity;
namespace Bit.Core.AdminConsole.OrganizationFeatures.AccountRecovery;
public class AdminRecoverAccountCommand(IOrganizationRepository organizationRepository,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IUserRepository userRepository,
IMailService mailService,
IEventService eventService,
@@ -30,9 +30,8 @@ public class AdminRecoverAccountCommand(IOrganizationRepository organizationRepo
}
// Enterprise policy must be enabled
- var resetPasswordPolicy =
- await policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
- if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled)
+ var resetPasswordPolicy = await policyQuery.RunAsync(orgId, PolicyType.ResetPassword);
+ if (!resetPasswordPolicy.Enabled)
{
throw new BadRequestException("Organization does not have the password reset policy enabled.");
}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUsersValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUsersValidator.cs
index 3375120516..f067f529ea 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUsersValidator.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUsersValidator.cs
@@ -3,7 +3,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimed
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Utilities.v2;
using Bit.Core.AdminConsole.Utilities.v2.Validation;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
@@ -20,7 +19,7 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
IPolicyRequirementQuery policyRequirementQuery,
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator,
IUserService userService,
- IPolicyRepository policyRepository) : IAutomaticallyConfirmOrganizationUsersValidator
+ IPolicyQuery policyQuery) : IAutomaticallyConfirmOrganizationUsersValidator
{
public async Task> ValidateAsync(
AutomaticallyConfirmOrganizationUserValidationRequest request)
@@ -74,7 +73,7 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
}
private async Task OrganizationHasAutomaticallyConfirmUsersPolicyEnabledAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) =>
- await policyRepository.GetByOrganizationIdTypeAsync(request.OrganizationId, PolicyType.AutomaticUserConfirmation) is { Enabled: true }
+ (await policyQuery.RunAsync(request.OrganizationId, PolicyType.AutomaticUserConfirmation)).Enabled
&& request.Organization is { UseAutomaticUserConfirmation: true };
private async Task OrganizationUserConformsToTwoFactorRequiredPolicyAsync(AutomaticallyConfirmOrganizationUserValidationRequest request)
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/SendOrganizationInvitesCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/SendOrganizationInvitesCommand.cs
index cd5066d11b..61f428414f 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/SendOrganizationInvitesCommand.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/SendOrganizationInvitesCommand.cs
@@ -4,7 +4,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
-using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.Auth.Models.Business;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Auth.Repositories;
@@ -19,7 +19,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUse
public class SendOrganizationInvitesCommand(
IUserRepository userRepository,
ISsoConfigRepository ssoConfigurationRepository,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IOrgUserInviteTokenableFactory orgUserInviteTokenableFactory,
IDataProtectorTokenFactory dataProtectorTokenFactory,
IMailService mailService) : ISendOrganizationInvitesCommand
@@ -58,7 +58,7 @@ public class SendOrganizationInvitesCommand(
// need to check the policy if the org has SSO enabled.
var orgSsoLoginRequiredPolicyEnabled = orgSsoEnabled &&
organization.UsePolicies &&
- (await policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.RequireSso))?.Enabled == true;
+ (await policyQuery.RunAsync(organization.Id, PolicyType.RequireSso)).Enabled;
// Generate the list of org users and expiring tokens
// create helper function to create expiring tokens
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyQuery.cs
new file mode 100644
index 0000000000..02eeeaa847
--- /dev/null
+++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyQuery.cs
@@ -0,0 +1,17 @@
+using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+
+namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies;
+
+public interface IPolicyQuery
+{
+ ///
+ /// Retrieves a summary view of an organization's usage of a policy specified by the .
+ ///
+ ///
+ /// This query is the entrypoint for consumers interested in understanding how a particular
+ /// has been applied to an organization; the resultant is not indicative of explicit
+ /// policy configuration.
+ ///
+ Task RunAsync(Guid organizationId, PolicyType policyType);
+}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs
new file mode 100644
index 0000000000..0ee6f9ab06
--- /dev/null
+++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs
@@ -0,0 +1,14 @@
+using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+using Bit.Core.AdminConsole.Repositories;
+
+namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
+
+public class PolicyQuery(IPolicyRepository policyRepository) : IPolicyQuery
+{
+ public async Task RunAsync(Guid organizationId, PolicyType policyType)
+ {
+ var dbPolicy = await policyRepository.GetByOrganizationIdTypeAsync(organizationId, policyType);
+ return new PolicyStatus(organizationId, policyType, dbPolicy);
+ }
+}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs
index f69935715d..6e0c3aa8d9 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs
@@ -18,6 +18,7 @@ public static class PolicyServiceCollectionExtensions
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddPolicyValidators();
diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs
index b51842398d..d87bc65042 100644
--- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs
+++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs
@@ -48,7 +48,7 @@ public class OrganizationService : IOrganizationService
private readonly IEventService _eventService;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IStripePaymentService _paymentService;
- private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly IPolicyService _policyService;
private readonly ISsoUserRepository _ssoUserRepository;
private readonly IGlobalSettings _globalSettings;
@@ -75,7 +75,7 @@ public class OrganizationService : IOrganizationService
IEventService eventService,
IApplicationCacheService applicationCacheService,
IStripePaymentService paymentService,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IPolicyService policyService,
ISsoUserRepository ssoUserRepository,
IGlobalSettings globalSettings,
@@ -102,7 +102,7 @@ public class OrganizationService : IOrganizationService
_eventService = eventService;
_applicationCacheService = applicationCacheService;
_paymentService = paymentService;
- _policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_policyService = policyService;
_ssoUserRepository = ssoUserRepository;
_globalSettings = globalSettings;
@@ -835,9 +835,8 @@ public class OrganizationService : IOrganizationService
}
// Make sure the organization has the policy enabled
- var resetPasswordPolicy =
- await _policyRepository.GetByOrganizationIdTypeAsync(organizationId, PolicyType.ResetPassword);
- if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled)
+ var resetPasswordPolicy = await _policyQuery.RunAsync(organizationId, PolicyType.ResetPassword);
+ if (!resetPasswordPolicy.Enabled)
{
throw new BadRequestException("Organization does not have the password reset policy enabled.");
}
diff --git a/src/Core/Auth/Services/Implementations/SsoConfigService.cs b/src/Core/Auth/Services/Implementations/SsoConfigService.cs
index 0cb8b68042..3c4f1ef85d 100644
--- a/src/Core/Auth/Services/Implementations/SsoConfigService.cs
+++ b/src/Core/Auth/Services/Implementations/SsoConfigService.cs
@@ -5,9 +5,9 @@ using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
-using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Repositories;
@@ -21,7 +21,7 @@ namespace Bit.Core.Auth.Services;
public class SsoConfigService : ISsoConfigService
{
private readonly ISsoConfigRepository _ssoConfigRepository;
- private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IEventService _eventService;
@@ -29,14 +29,14 @@ public class SsoConfigService : ISsoConfigService
public SsoConfigService(
ISsoConfigRepository ssoConfigRepository,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IEventService eventService,
IVNextSavePolicyCommand vNextSavePolicyCommand)
{
_ssoConfigRepository = ssoConfigRepository;
- _policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_eventService = eventService;
@@ -114,14 +114,14 @@ public class SsoConfigService : ISsoConfigService
throw new BadRequestException("Organization cannot use Key Connector.");
}
- var singleOrgPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.SingleOrg);
- if (singleOrgPolicy is not { Enabled: true })
+ var singleOrgPolicy = await _policyQuery.RunAsync(config.OrganizationId, PolicyType.SingleOrg);
+ if (!singleOrgPolicy.Enabled)
{
throw new BadRequestException("Key Connector requires the Single Organization policy to be enabled.");
}
- var ssoPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.RequireSso);
- if (ssoPolicy is not { Enabled: true })
+ var ssoPolicy = await _policyQuery.RunAsync(config.OrganizationId, PolicyType.RequireSso);
+ if (!ssoPolicy.Enabled)
{
throw new BadRequestException("Key Connector requires the Single Sign-On Authentication policy to be enabled.");
}
diff --git a/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs b/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs
index 4a0e9c2cf5..ba63afb54c 100644
--- a/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs
+++ b/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs
@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
-using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Auth.Models.Business.Tokenables;
@@ -27,7 +27,7 @@ public class RegisterUserCommand : IRegisterUserCommand
private readonly IGlobalSettings _globalSettings;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationRepository _organizationRepository;
- private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IFeatureService _featureService;
@@ -50,7 +50,7 @@ public class RegisterUserCommand : IRegisterUserCommand
IGlobalSettings globalSettings,
IOrganizationUserRepository organizationUserRepository,
IOrganizationRepository organizationRepository,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IOrganizationDomainRepository organizationDomainRepository,
IFeatureService featureService,
IDataProtectionProvider dataProtectionProvider,
@@ -65,7 +65,7 @@ public class RegisterUserCommand : IRegisterUserCommand
_globalSettings = globalSettings;
_organizationUserRepository = organizationUserRepository;
_organizationRepository = organizationRepository;
- _policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_organizationDomainRepository = organizationDomainRepository;
_featureService = featureService;
@@ -246,9 +246,9 @@ public class RegisterUserCommand : IRegisterUserCommand
var orgUser = await _organizationUserRepository.GetByIdAsync(orgUserId.Value);
if (orgUser != null)
{
- var twoFactorPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgUser.OrganizationId,
+ var twoFactorPolicy = await _policyQuery.RunAsync(orgUser.OrganizationId,
PolicyType.TwoFactorAuthentication);
- if (twoFactorPolicy != null && twoFactorPolicy.Enabled)
+ if (twoFactorPolicy.Enabled)
{
user.SetTwoFactorProviders(new Dictionary
{
diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs
index 4ad63bd8d7..9c06ce1709 100644
--- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs
@@ -5,6 +5,7 @@ using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.OrganizationConnectionConfigs;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Repositories;
@@ -30,6 +31,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
private readonly IGroupRepository _groupRepository;
private readonly IStripePaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
@@ -45,6 +47,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
IGroupRepository groupRepository,
IStripePaymentService paymentService,
IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
ISsoConfigRepository ssoConfigRepository,
IOrganizationConnectionRepository organizationConnectionRepository,
IServiceAccountRepository serviceAccountRepository,
@@ -59,6 +62,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
_groupRepository = groupRepository;
_paymentService = paymentService;
_policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_ssoConfigRepository = ssoConfigRepository;
_organizationConnectionRepository = organizationConnectionRepository;
_serviceAccountRepository = serviceAccountRepository;
@@ -184,9 +188,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
if (!newPlan.HasResetPassword && organization.UseResetPassword)
{
- var resetPasswordPolicy =
- await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
- if (resetPasswordPolicy != null && resetPasswordPolicy.Enabled)
+ var resetPasswordPolicy = await _policyQuery.RunAsync(organization.Id, PolicyType.ResetPassword);
+ if (resetPasswordPolicy.Enabled)
{
throw new BadRequestException("Your new plan does not allow the Password Reset feature. " +
"Disable your Password Reset policy.");
diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs
index 64caf1d462..5f87ee85d2 100644
--- a/src/Core/Services/Implementations/UserService.cs
+++ b/src/Core/Services/Implementations/UserService.cs
@@ -61,7 +61,7 @@ public class UserService : UserManager, IUserService
private readonly IEventService _eventService;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IStripePaymentService _paymentService;
- private readonly IPolicyRepository _policyRepository;
+ private readonly IPolicyQuery _policyQuery;
private readonly IPolicyService _policyService;
private readonly IFido2 _fido2;
private readonly ICurrentContext _currentContext;
@@ -98,7 +98,7 @@ public class UserService : UserManager, IUserService
IEventService eventService,
IApplicationCacheService applicationCacheService,
IStripePaymentService paymentService,
- IPolicyRepository policyRepository,
+ IPolicyQuery policyQuery,
IPolicyService policyService,
IFido2 fido2,
ICurrentContext currentContext,
@@ -139,7 +139,7 @@ public class UserService : UserManager, IUserService
_eventService = eventService;
_applicationCacheService = applicationCacheService;
_paymentService = paymentService;
- _policyRepository = policyRepository;
+ _policyQuery = policyQuery;
_policyService = policyService;
_fido2 = fido2;
_currentContext = currentContext;
@@ -722,9 +722,8 @@ public class UserService : UserManager, IUserService
}
// Enterprise policy must be enabled
- var resetPasswordPolicy =
- await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
- if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled)
+ var resetPasswordPolicy = await _policyQuery.RunAsync(orgId, PolicyType.ResetPassword);
+ if (!resetPasswordPolicy.Enabled)
{
throw new BadRequestException("Organization does not have the password reset policy enabled.");
}
diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
index d97a1be793..68a63bf579 100644
--- a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
+++ b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs
@@ -14,7 +14,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Utilities.v2.Results;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Repositories;
@@ -30,6 +29,7 @@ using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
+using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
@@ -137,23 +137,20 @@ public class OrganizationUsersControllerTests
[Theory]
[BitAutoData]
public async Task Accept_WhenOrganizationUsePoliciesIsEnabledAndResetPolicyIsEnabled_ShouldHandleResetPassword(Guid orgId, Guid orgUserId,
- OrganizationUserAcceptRequestModel model, User user, SutProvider sutProvider)
+ OrganizationUserAcceptRequestModel model, User user,
+ [Policy(PolicyType.ResetPassword, true)] PolicyStatus policy,
+ SutProvider sutProvider)
{
// Arrange
var applicationCacheService = sutProvider.GetDependency();
applicationCacheService.GetOrganizationAbilityAsync(orgId).Returns(new OrganizationAbility { UsePolicies = true });
- var policy = new Policy
- {
- Enabled = true,
- Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }),
- };
+ policy.Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, });
var userService = sutProvider.GetDependency();
userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
-
- var policyRepository = sutProvider.GetDependency();
- policyRepository.GetByOrganizationIdTypeAsync(orgId,
+ var policyQuery = sutProvider.GetDependency();
+ policyQuery.RunAsync(orgId,
PolicyType.ResetPassword).Returns(policy);
// Act
@@ -167,29 +164,27 @@ public class OrganizationUsersControllerTests
await userService.Received(1).GetUserByPrincipalAsync(default);
await applicationCacheService.Received(1).GetOrganizationAbilityAsync(orgId);
- await policyRepository.Received(1).GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
+ await policyQuery.Received(1).RunAsync(orgId, PolicyType.ResetPassword);
}
[Theory]
[BitAutoData]
public async Task Accept_WhenOrganizationUsePoliciesIsDisabled_ShouldNotHandleResetPassword(Guid orgId, Guid orgUserId,
- OrganizationUserAcceptRequestModel model, User user, SutProvider sutProvider)
+ OrganizationUserAcceptRequestModel model, User user,
+ [Policy(PolicyType.ResetPassword, true)] PolicyStatus policy,
+ SutProvider sutProvider)
{
// Arrange
var applicationCacheService = sutProvider.GetDependency();
applicationCacheService.GetOrganizationAbilityAsync(orgId).Returns(new OrganizationAbility { UsePolicies = false });
- var policy = new Policy
- {
- Enabled = true,
- Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }),
- };
+ policy.Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, });
var userService = sutProvider.GetDependency();
userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
- var policyRepository = sutProvider.GetDependency();
- policyRepository.GetByOrganizationIdTypeAsync(orgId,
+ var policyQuery = sutProvider.GetDependency();
+ policyQuery.RunAsync(orgId,
PolicyType.ResetPassword).Returns(policy);
// Act
@@ -202,7 +197,7 @@ public class OrganizationUsersControllerTests
await sutProvider.GetDependency().Received(0)
.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
- await policyRepository.Received(0).GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
+ await policyQuery.Received(0).RunAsync(orgId, PolicyType.ResetPassword);
await applicationCacheService.Received(1).GetOrganizationAbilityAsync(orgId);
}
@@ -383,7 +378,7 @@ public class OrganizationUsersControllerTests
var policyRequirementQuery = sutProvider.GetDependency();
- var policyRepository = sutProvider.GetDependency();
+ var policyQuery = sutProvider.GetDependency();
var policyRequirement = new ResetPasswordPolicyRequirement { AutoEnrollOrganizations = [orgId] };
@@ -400,7 +395,7 @@ public class OrganizationUsersControllerTests
await userService.Received(1).GetUserByPrincipalAsync(default);
await applicationCacheService.Received(0).GetOrganizationAbilityAsync(orgId);
- await policyRepository.Received(0).GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
+ await policyQuery.Received(0).RunAsync(orgId, PolicyType.ResetPassword);
await policyRequirementQuery.Received(1).GetAsync(user.Id);
Assert.True(policyRequirement.AutoEnrollEnabled(orgId));
}
@@ -425,7 +420,7 @@ public class OrganizationUsersControllerTests
var userService = sutProvider.GetDependency();
userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
- var policyRepository = sutProvider.GetDependency();
+ var policyQuery = sutProvider.GetDependency();
var policyRequirementQuery = sutProvider.GetDependency();
@@ -445,7 +440,7 @@ public class OrganizationUsersControllerTests
await userService.Received(1).GetUserByPrincipalAsync(default);
await applicationCacheService.Received(0).GetOrganizationAbilityAsync(orgId);
- await policyRepository.Received(0).GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
+ await policyQuery.Received(0).RunAsync(orgId, PolicyType.ResetPassword);
await policyRequirementQuery.Received(1).GetAsync(user.Id);
Assert.Equal("Master Password reset is required, but not provided.", exception.Message);
diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs
index d87f035a13..cc09e9e0a0 100644
--- a/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs
+++ b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs
@@ -7,6 +7,7 @@ using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
@@ -25,6 +26,7 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
+using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Test.Billing.Mocks;
using Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider;
using Bit.Test.Common.AutoFixture;
@@ -200,28 +202,21 @@ public class OrganizationsControllerTests
SutProvider sutProvider,
User user,
Organization organization,
- OrganizationUser organizationUser)
+ OrganizationUser organizationUser,
+ [Policy(PolicyType.ResetPassword, data: "{\"AutoEnrollEnabled\": true}")] PolicyStatus policy)
{
- var policy = new Policy
- {
- Type = PolicyType.ResetPassword,
- Enabled = true,
- Data = "{\"AutoEnrollEnabled\": true}",
- OrganizationId = organization.Id
- };
-
sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).Returns(user);
sutProvider.GetDependency().GetByIdentifierAsync(organization.Id.ToString()).Returns(organization);
sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(false);
sutProvider.GetDependency().GetByOrganizationAsync(organization.Id, user.Id).Returns(organizationUser);
- sutProvider.GetDependency().GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword).Returns(policy);
+ sutProvider.GetDependency().RunAsync(organization.Id, PolicyType.ResetPassword).Returns(policy);
var result = await sutProvider.Sut.GetAutoEnrollStatus(organization.Id.ToString());
await sutProvider.GetDependency().Received(1).GetUserByPrincipalAsync(Arg.Any());
await sutProvider.GetDependency().Received(1).GetByIdentifierAsync(organization.Id.ToString());
await sutProvider.GetDependency().Received(0).GetAsync(user.Id);
- await sutProvider.GetDependency().Received(1).GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
+ await sutProvider.GetDependency().Received(1).RunAsync(organization.Id, PolicyType.ResetPassword);
Assert.True(result.ResetPasswordEnabled);
}
diff --git a/test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyDetailResponsesTests.cs b/test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyStatusResponsesTests.cs
similarity index 62%
rename from test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyDetailResponsesTests.cs
rename to test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyStatusResponsesTests.cs
index 9b863091db..46c6d64bdd 100644
--- a/test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyDetailResponsesTests.cs
+++ b/test/Api.Test/AdminConsole/Models/Response/Helpers/PolicyStatusResponsesTests.cs
@@ -1,14 +1,13 @@
-using AutoFixture;
-using Bit.Api.AdminConsole.Models.Response.Helpers;
-using Bit.Core.AdminConsole.Entities;
+using Bit.Api.AdminConsole.Models.Response.Helpers;
using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Models.Response.Helpers;
-public class PolicyDetailResponsesTests
+public class PolicyStatusResponsesTests
{
[Theory]
[InlineData(true, false)]
@@ -17,19 +16,13 @@ public class PolicyDetailResponsesTests
bool policyEnabled,
bool expectedCanToggle)
{
- var fixture = new Fixture();
-
- var policy = fixture.Build()
- .Without(p => p.Data)
- .With(p => p.Type, PolicyType.SingleOrg)
- .With(p => p.Enabled, policyEnabled)
- .Create();
+ var policy = new PolicyStatus(Guid.NewGuid(), PolicyType.SingleOrg) { Enabled = policyEnabled };
var querySub = Substitute.For();
querySub.HasVerifiedDomainsAsync(policy.OrganizationId)
.Returns(true);
- var result = await policy.GetSingleOrgPolicyDetailResponseAsync(querySub);
+ var result = await policy.GetSingleOrgPolicyStatusResponseAsync(querySub);
Assert.Equal(expectedCanToggle, result.CanToggleState);
}
@@ -37,18 +30,13 @@ public class PolicyDetailResponsesTests
[Fact]
public async Task GetSingleOrgPolicyDetailResponseAsync_WhenIsNotSingleOrgType_ThenShouldThrowArgumentException()
{
- var fixture = new Fixture();
-
- var policy = fixture.Build()
- .Without(p => p.Data)
- .With(p => p.Type, PolicyType.TwoFactorAuthentication)
- .Create();
+ var policy = new PolicyStatus(Guid.NewGuid(), PolicyType.TwoFactorAuthentication);
var querySub = Substitute.For();
querySub.HasVerifiedDomainsAsync(policy.OrganizationId)
.Returns(true);
- var action = async () => await policy.GetSingleOrgPolicyDetailResponseAsync(querySub);
+ var action = async () => await policy.GetSingleOrgPolicyStatusResponseAsync(querySub);
await Assert.ThrowsAsync("policy", action);
}
@@ -56,18 +44,13 @@ public class PolicyDetailResponsesTests
[Fact]
public async Task GetSingleOrgPolicyDetailResponseAsync_WhenIsSingleOrgTypeAndDoesNotHaveVerifiedDomains_ThenShouldBeAbleToToggle()
{
- var fixture = new Fixture();
-
- var policy = fixture.Build()
- .Without(p => p.Data)
- .With(p => p.Type, PolicyType.SingleOrg)
- .Create();
+ var policy = new PolicyStatus(Guid.NewGuid(), PolicyType.SingleOrg);
var querySub = Substitute.For();
querySub.HasVerifiedDomainsAsync(policy.OrganizationId)
.Returns(false);
- var result = await policy.GetSingleOrgPolicyDetailResponseAsync(querySub);
+ var result = await policy.GetSingleOrgPolicyStatusResponseAsync(querySub);
Assert.True(result.CanToggleState);
}
diff --git a/test/Api.Test/Billing/Controllers/OrganizationSponsorshipsControllerTests.cs b/test/Api.Test/Billing/Controllers/OrganizationSponsorshipsControllerTests.cs
index 87334dc085..a7eb4dda5e 100644
--- a/test/Api.Test/Billing/Controllers/OrganizationSponsorshipsControllerTests.cs
+++ b/test/Api.Test/Billing/Controllers/OrganizationSponsorshipsControllerTests.cs
@@ -1,6 +1,9 @@
using Bit.Api.Billing.Controllers;
using Bit.Api.Models.Request.Organizations;
using Bit.Core.AdminConsole.Entities;
+using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.Billing.Enums;
using Bit.Core.Context;
using Bit.Core.Entities;
@@ -10,6 +13,7 @@ using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
+using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Test.Billing.Mocks;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
@@ -82,7 +86,9 @@ public class OrganizationSponsorshipsControllerTests
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, Organization sponsoringOrganization,
- OrganizationSponsorshipRedeemRequestModel model, SutProvider sutProvider)
+ OrganizationSponsorshipRedeemRequestModel model,
+ [Policy(PolicyType.FreeFamiliesSponsorshipPolicy, false)] PolicyStatus policy,
+ SutProvider sutProvider)
{
sutProvider.GetDependency().UserId.Returns(user.Id);
sutProvider.GetDependency().GetUserByIdAsync(user.Id)
@@ -91,6 +97,9 @@ public class OrganizationSponsorshipsControllerTests
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency().OrganizationOwner(model.SponsoredOrganizationId).Returns(true);
sutProvider.GetDependency().GetByIdAsync(model.SponsoredOrganizationId).Returns(sponsoringOrganization);
+ sutProvider.GetDependency()
+ .RunAsync(Arg.Any(), PolicyType.FreeFamiliesSponsorshipPolicy)
+ .Returns(policy);
await sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model);
@@ -101,14 +110,18 @@ public class OrganizationSponsorshipsControllerTests
[Theory]
[BitAutoData]
public async Task PreValidateSponsorshipToken_ValidatesToken_Success(string sponsorshipToken, User user,
- OrganizationSponsorship sponsorship, SutProvider sutProvider)
+ OrganizationSponsorship sponsorship,
+ [Policy(PolicyType.FreeFamiliesSponsorshipPolicy, false)] PolicyStatus policy,
+ SutProvider sutProvider)
{
sutProvider.GetDependency().UserId.Returns(user.Id);
sutProvider.GetDependency().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency()
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email).Returns((true, sponsorship));
-
+ sutProvider.GetDependency()
+ .RunAsync(Arg.Any(), PolicyType.FreeFamiliesSponsorshipPolicy)
+ .Returns(policy);
await sutProvider.Sut.PreValidateSponsorshipToken(sponsorshipToken);
await sutProvider.GetDependency().Received(1)
diff --git a/test/Api.Test/Controllers/PoliciesControllerTests.cs b/test/Api.Test/Controllers/PoliciesControllerTests.cs
index efb9f7aaa9..03ab20ec28 100644
--- a/test/Api.Test/Controllers/PoliciesControllerTests.cs
+++ b/test/Api.Test/Controllers/PoliciesControllerTests.cs
@@ -49,7 +49,7 @@ public class PoliciesControllerTests
sutProvider.GetDependency()
.GetProperUserId(Arg.Any())
- .Returns((Guid?)userId);
+ .Returns(userId);
sutProvider.GetDependency()
.GetByOrganizationAsync(orgId, userId)
@@ -95,7 +95,7 @@ public class PoliciesControllerTests
// Arrange
sutProvider.GetDependency()
.GetProperUserId(Arg.Any())
- .Returns((Guid?)userId);
+ .Returns(userId);
sutProvider.GetDependency()
.GetByOrganizationAsync(orgId, userId)
@@ -113,7 +113,7 @@ public class PoliciesControllerTests
// Arrange
sutProvider.GetDependency()
.GetProperUserId(Arg.Any())
- .Returns((Guid?)userId);
+ .Returns(userId);
sutProvider.GetDependency()
.GetByOrganizationAsync(orgId, userId)
@@ -135,7 +135,7 @@ public class PoliciesControllerTests
// Arrange
sutProvider.GetDependency()
.GetProperUserId(Arg.Any())
- .Returns((Guid?)userId);
+ .Returns(userId);
sutProvider.GetDependency()
.GetByOrganizationAsync(orgId, userId)
@@ -186,59 +186,35 @@ public class PoliciesControllerTests
[Theory]
[BitAutoData]
public async Task Get_WhenUserCanManagePolicies_WithExistingType_ReturnsExistingPolicy(
- SutProvider sutProvider, Guid orgId, Policy policy, int type)
+ SutProvider sutProvider, Guid orgId, PolicyStatus policy, PolicyType type)
{
// Arrange
sutProvider.GetDependency()
.ManagePolicies(orgId)
.Returns(true);
- policy.Type = (PolicyType)type;
+ policy.Type = type;
policy.Enabled = true;
policy.Data = null;
- sutProvider.GetDependency()
- .GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
+ sutProvider.GetDependency()
+ .RunAsync(orgId, type)
.Returns(policy);
// Act
var result = await sutProvider.Sut.Get(orgId, type);
// Assert
- Assert.IsType(result);
- Assert.Equal(policy.Id, result.Id);
+ Assert.IsType(result);
Assert.Equal(policy.Type, result.Type);
Assert.Equal(policy.Enabled, result.Enabled);
Assert.Equal(policy.OrganizationId, result.OrganizationId);
}
- [Theory]
- [BitAutoData]
- public async Task Get_WhenUserCanManagePolicies_WithNonExistingType_ReturnsDefaultPolicy(
- SutProvider sutProvider, Guid orgId, int type)
- {
- // Arrange
- sutProvider.GetDependency()
- .ManagePolicies(orgId)
- .Returns(true);
-
- sutProvider.GetDependency()
- .GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
- .Returns((Policy)null);
-
- // Act
- var result = await sutProvider.Sut.Get(orgId, type);
-
- // Assert
- Assert.IsType(result);
- Assert.Equal(result.Type, (PolicyType)type);
- Assert.False(result.Enabled);
- }
-
[Theory]
[BitAutoData]
public async Task Get_WhenUserCannotManagePolicies_ThrowsNotFoundException(
- SutProvider sutProvider, Guid orgId, int type)
+ SutProvider sutProvider, Guid orgId, PolicyType type)
{
// Arrange
sutProvider.GetDependency()
diff --git a/test/Core.Test/AdminConsole/AutoFixture/PolicyFixtures.cs b/test/Core.Test/AdminConsole/AutoFixture/PolicyFixtures.cs
index 09b112c43c..01ffb86a7d 100644
--- a/test/Core.Test/AdminConsole/AutoFixture/PolicyFixtures.cs
+++ b/test/Core.Test/AdminConsole/AutoFixture/PolicyFixtures.cs
@@ -3,6 +3,7 @@ using AutoFixture;
using AutoFixture.Xunit2;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
namespace Bit.Core.Test.AdminConsole.AutoFixture;
@@ -10,19 +11,30 @@ internal class PolicyCustomization : ICustomization
{
public PolicyType Type { get; set; }
public bool Enabled { get; set; }
+ public string? Data { get; set; }
- public PolicyCustomization(PolicyType type, bool enabled)
+ public PolicyCustomization(PolicyType type, bool enabled, string? data)
{
Type = type;
Enabled = enabled;
+ Data = data;
}
public void Customize(IFixture fixture)
{
+ var orgId = Guid.NewGuid();
+
fixture.Customize(composer => composer
- .With(o => o.OrganizationId, Guid.NewGuid())
+ .With(o => o.OrganizationId, orgId)
.With(o => o.Type, Type)
- .With(o => o.Enabled, Enabled));
+ .With(o => o.Enabled, Enabled)
+ .With(o => o.Data, Data));
+
+ fixture.Customize(composer => composer
+ .With(o => o.OrganizationId, orgId)
+ .With(o => o.Type, Type)
+ .With(o => o.Enabled, Enabled)
+ .With(o => o.Data, Data));
}
}
@@ -30,15 +42,17 @@ public class PolicyAttribute : CustomizeAttribute
{
private readonly PolicyType _type;
private readonly bool _enabled;
+ private readonly string? _data;
- public PolicyAttribute(PolicyType type, bool enabled = true)
+ public PolicyAttribute(PolicyType type, bool enabled = true, string? data = null)
{
_type = type;
_enabled = enabled;
+ _data = data;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
- return new PolicyCustomization(_type, _enabled);
+ return new PolicyCustomization(_type, _enabled, _data);
}
}
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/AccountRecovery/AdminRecoverAccountCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/AccountRecovery/AdminRecoverAccountCommandTests.cs
index 88025301b6..3095907a22 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/AccountRecovery/AdminRecoverAccountCommandTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/AccountRecovery/AdminRecoverAccountCommandTests.cs
@@ -1,14 +1,16 @@
using AutoFixture;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.AccountRecovery;
-using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;
+using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
@@ -29,11 +31,12 @@ public class AdminRecoverAccountCommandTests
Organization organization,
OrganizationUser organizationUser,
User user,
+ [Policy(PolicyType.ResetPassword, true)] PolicyStatus policy,
SutProvider sutProvider)
{
// Arrange
SetupValidOrganization(sutProvider, organization);
- SetupValidPolicy(sutProvider, organization);
+ SetupValidPolicy(sutProvider, organization, policy);
SetupValidOrganizationUser(organizationUser, organization.Id);
SetupValidUser(sutProvider, user, organizationUser);
SetupSuccessfulPasswordUpdate(sutProvider, user, newMasterPassword);
@@ -87,25 +90,18 @@ public class AdminRecoverAccountCommandTests
Assert.Equal("Organization does not allow password reset.", exception.Message);
}
- public static IEnumerable