mirror of
https://github.com/bitwarden/server
synced 2025-12-25 04:33:26 +00:00
[PM-27131] Auto confirm policy requirement (#6649)
* Added Auto confirm policy enforcement requirement. Includes strict single org enforcement along with blocking provider users from joining orgs with auto confirm enabled.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
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.Services;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
@@ -34,6 +35,7 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
private readonly IAutomaticUserConfirmationPolicyEnforcementValidator _automaticUserConfirmationPolicyEnforcementValidator;
|
||||
|
||||
public AcceptOrgUserCommand(
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
@@ -46,7 +48,8 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
||||
IFeatureService featureService,
|
||||
IPolicyRequirementQuery policyRequirementQuery)
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator)
|
||||
{
|
||||
// TODO: remove data protector when old token validation removed
|
||||
_dataProtector = dataProtectionProvider.CreateProtector(OrgUserInviteTokenable.DataProtectorPurpose);
|
||||
@@ -60,6 +63,7 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||
_featureService = featureService;
|
||||
_policyRequirementQuery = policyRequirementQuery;
|
||||
_automaticUserConfirmationPolicyEnforcementValidator = automaticUserConfirmationPolicyEnforcementValidator;
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> AcceptOrgUserByEmailTokenAsync(Guid organizationUserId, User user, string emailToken,
|
||||
@@ -186,13 +190,19 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce Single Organization Policy of organization user is trying to join
|
||||
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
||||
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
|
||||
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
await ValidateAutomaticUserConfirmationPolicyAsync(orgUser, allOrgUsers, user);
|
||||
}
|
||||
|
||||
// Enforce Single Organization Policy of organization user is trying to join
|
||||
var invitedSingleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
||||
PolicyType.SingleOrg, OrganizationUserStatusType.Invited);
|
||||
|
||||
if (hasOtherOrgs && invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||
if (allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId)
|
||||
&& invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||
{
|
||||
throw new BadRequestException("You may not join this organization until you leave or remove all other organizations.");
|
||||
}
|
||||
@@ -255,4 +265,20 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateAutomaticUserConfirmationPolicyAsync(OrganizationUser orgUser,
|
||||
ICollection<OrganizationUser> allOrgUsers, User user)
|
||||
{
|
||||
var error = (await _automaticUserConfirmationPolicyEnforcementValidator.IsCompliantAsync(
|
||||
new AutomaticUserConfirmationPolicyEnforcementRequest(orgUser.OrganizationId, allOrgUsers, user)))
|
||||
.Match(
|
||||
error => error.Message,
|
||||
_ => string.Empty
|
||||
);
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
throw new BadRequestException(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount;
|
||||
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;
|
||||
@@ -8,6 +9,7 @@ using Bit.Core.AdminConsole.Utilities.v2.Validation;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
|
||||
@@ -16,6 +18,8 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator,
|
||||
IUserService userService,
|
||||
IPolicyRepository policyRepository) : IAutomaticallyConfirmOrganizationUsersValidator
|
||||
{
|
||||
public async Task<ValidationResult<AutomaticallyConfirmOrganizationUserValidationRequest>> ValidateAsync(
|
||||
@@ -61,7 +65,7 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
|
||||
return Invalid(request, new UserDoesNotHaveTwoFactorEnabled());
|
||||
}
|
||||
|
||||
if (await OrganizationUserConformsToSingleOrgPolicyAsync(request) is { } error)
|
||||
if (await OrganizationUserConformsToAutomaticUserConfirmationPolicyAsync(request) is { } error)
|
||||
{
|
||||
return Invalid(request, error);
|
||||
}
|
||||
@@ -69,10 +73,8 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
|
||||
return Valid(request);
|
||||
}
|
||||
|
||||
private async Task<bool> OrganizationHasAutomaticallyConfirmUsersPolicyEnabledAsync(
|
||||
AutomaticallyConfirmOrganizationUserValidationRequest request) =>
|
||||
await policyRepository.GetByOrganizationIdTypeAsync(request.OrganizationId,
|
||||
PolicyType.AutomaticUserConfirmation) is { Enabled: true }
|
||||
private async Task<bool> OrganizationHasAutomaticallyConfirmUsersPolicyEnabledAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) =>
|
||||
await policyRepository.GetByOrganizationIdTypeAsync(request.OrganizationId, PolicyType.AutomaticUserConfirmation) is { Enabled: true }
|
||||
&& request.Organization is { UseAutomaticUserConfirmation: true };
|
||||
|
||||
private async Task<bool> OrganizationUserConformsToTwoFactorRequiredPolicyAsync(AutomaticallyConfirmOrganizationUserValidationRequest request)
|
||||
@@ -87,30 +89,37 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
|
||||
.IsTwoFactorRequiredForOrganization(request.Organization!.Id);
|
||||
}
|
||||
|
||||
private async Task<Error?> OrganizationUserConformsToSingleOrgPolicyAsync(
|
||||
/// <summary>
|
||||
/// Validates whether the specified organization user complies with the automatic user confirmation policy.
|
||||
/// This includes checks across all organizations the user is associated with to ensure they meet the compliance criteria.
|
||||
///
|
||||
/// We are not checking single organization policy compliance here because automatically confirm users policy enforces
|
||||
/// a stricter version and applies to all users. If you are compliant with Auto Confirm, you'll be in compliance with
|
||||
/// Single Org.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// The request model encapsulates the current organization, the user being validated, and all organization users associated
|
||||
/// with that user.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// An <see cref="Error"/> if the user fails to meet the automatic user confirmation policy, or null if the validation succeeds.
|
||||
/// </returns>
|
||||
private async Task<Error?> OrganizationUserConformsToAutomaticUserConfirmationPolicyAsync(
|
||||
AutomaticallyConfirmOrganizationUserValidationRequest request)
|
||||
{
|
||||
var allOrganizationUsersForUser = await organizationUserRepository
|
||||
.GetManyByUserAsync(request.OrganizationUser!.UserId!.Value);
|
||||
|
||||
if (allOrganizationUsersForUser.Count == 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var user = await userService.GetUserByIdAsync(request.OrganizationUser!.UserId!.Value);
|
||||
|
||||
var policyRequirement = await policyRequirementQuery
|
||||
.GetAsync<SingleOrganizationPolicyRequirement>(request.OrganizationUser!.UserId!.Value);
|
||||
|
||||
if (policyRequirement.IsSingleOrgEnabledForThisOrganization(request.Organization!.Id))
|
||||
{
|
||||
return new OrganizationEnforcesSingleOrgPolicy();
|
||||
}
|
||||
|
||||
if (policyRequirement.IsSingleOrgEnabledForOrganizationsOtherThan(request.Organization.Id))
|
||||
{
|
||||
return new OtherOrganizationEnforcesSingleOrgPolicy();
|
||||
}
|
||||
|
||||
return null;
|
||||
return (await automaticUserConfirmationPolicyEnforcementValidator.IsCompliantAsync(
|
||||
new AutomaticUserConfirmationPolicyEnforcementRequest(
|
||||
request.OrganizationId,
|
||||
allOrganizationUsersForUser,
|
||||
user)))
|
||||
.Match<Error?>(
|
||||
error => error,
|
||||
_ => null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ public record UserIsNotUserType() : BadRequestError("Only organization users wit
|
||||
public record UserIsNotAccepted() : BadRequestError("Cannot confirm user that has not accepted the invitation.");
|
||||
public record OrganizationUserIdIsInvalid() : BadRequestError("Invalid organization user id.");
|
||||
public record UserDoesNotHaveTwoFactorEnabled() : BadRequestError("User does not have two-step login enabled.");
|
||||
public record OrganizationEnforcesSingleOrgPolicy() : BadRequestError("Cannot confirm this member to the organization until they leave or remove all other organizations");
|
||||
public record OtherOrganizationEnforcesSingleOrgPolicy() : BadRequestError("Cannot confirm this member to the organization because they are in another organization which forbids it.");
|
||||
public record UserCannotBelongToAnotherOrganization() : BadRequestError("Cannot confirm this member to the organization until they leave or remove all other organizations");
|
||||
public record OtherOrganizationDoesNotAllowOtherMembership() : BadRequestError("Cannot confirm this member to the organization because they are in another organization which forbids it.");
|
||||
public record AutomaticallyConfirmUsersPolicyIsNotEnabled() : BadRequestError("Cannot confirm this member because the Automatically Confirm Users policy is not enabled.");
|
||||
public record ProviderUsersCannotJoin() : BadRequestError("An organization the user is a part of has enabled Automatic User Confirmation policy, and it does not support provider users joining.");
|
||||
public record UserCannotJoinProvider() : BadRequestError("An organization the user is a part of has enabled Automatic User Confirmation policy, and it does not support the user joining a provider.");
|
||||
public record CurrentOrganizationUserIsNotPresentInRequest() : BadRequestError("The current organization user does not exist in the request.");
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
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.Services;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
@@ -33,6 +34,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IAutomaticUserConfirmationPolicyEnforcementValidator _automaticUserConfirmationPolicyEnforcementValidator;
|
||||
|
||||
public ConfirmOrganizationUserCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@@ -47,7 +49,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
IDeviceRepository deviceRepository,
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IFeatureService featureService,
|
||||
ICollectionRepository collectionRepository)
|
||||
ICollectionRepository collectionRepository,
|
||||
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@@ -62,6 +65,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
_policyRequirementQuery = policyRequirementQuery;
|
||||
_featureService = featureService;
|
||||
_collectionRepository = collectionRepository;
|
||||
_automaticUserConfirmationPolicyEnforcementValidator = automaticUserConfirmationPolicyEnforcementValidator;
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
|
||||
@@ -127,6 +131,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(organizationId);
|
||||
var allUsersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validSelectedUserIds);
|
||||
|
||||
var users = await _userRepository.GetManyAsync(validSelectedUserIds);
|
||||
var usersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(validSelectedUserIds);
|
||||
|
||||
@@ -188,6 +193,25 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
await ValidateTwoFactorAuthenticationPolicyAsync(user, organizationId, userTwoFactorEnabled);
|
||||
|
||||
var hasOtherOrgs = userOrgs.Any(ou => ou.OrganizationId != organizationId);
|
||||
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
var error = (await _automaticUserConfirmationPolicyEnforcementValidator.IsCompliantAsync(
|
||||
new AutomaticUserConfirmationPolicyEnforcementRequest(
|
||||
organizationId,
|
||||
userOrgs,
|
||||
user)))
|
||||
.Match(
|
||||
error => new BadRequestException(error.Message),
|
||||
_ => null
|
||||
);
|
||||
|
||||
if (error is not null)
|
||||
{
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
var singleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg);
|
||||
var otherSingleOrgPolicies =
|
||||
singleOrgPolicies.Where(p => p.OrganizationId != organizationId);
|
||||
@@ -267,8 +291,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
return;
|
||||
}
|
||||
|
||||
var organizationDataOwnershipPolicy =
|
||||
await _policyRequirementQuery.GetAsync<OrganizationDataOwnershipPolicyRequirement>(organizationUser.UserId!.Value);
|
||||
var organizationDataOwnershipPolicy = await _policyRequirementQuery.GetAsync<OrganizationDataOwnershipPolicyRequirement>(organizationUser.UserId!.Value);
|
||||
if (!organizationDataOwnershipPolicy.RequiresDefaultCollectionOnConfirm(organizationUser.OrganizationId))
|
||||
{
|
||||
return;
|
||||
@@ -311,8 +334,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
return;
|
||||
}
|
||||
|
||||
var policyEligibleOrganizationUserIds =
|
||||
await _policyRequirementQuery.GetManyByOrganizationIdAsync<OrganizationDataOwnershipPolicyRequirement>(organizationId);
|
||||
var policyEligibleOrganizationUserIds = await _policyRequirementQuery
|
||||
.GetManyByOrganizationIdAsync<OrganizationDataOwnershipPolicyRequirement>(organizationId);
|
||||
|
||||
var eligibleOrganizationUserIds = confirmedOrganizationUsers
|
||||
.Where(ou => policyEligibleOrganizationUserIds.Contains(ou.Id))
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
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.Services;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
@@ -29,7 +30,8 @@ public class RestoreOrganizationUserCommand(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationService organizationService,
|
||||
IFeatureService featureService,
|
||||
IPolicyRequirementQuery policyRequirementQuery) : IRestoreOrganizationUserCommand
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator) : IRestoreOrganizationUserCommand
|
||||
{
|
||||
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId)
|
||||
{
|
||||
@@ -300,6 +302,25 @@ public class RestoreOrganizationUserCommand(
|
||||
{
|
||||
throw new BadRequestException(user.Email + " is not compliant with the two-step login policy");
|
||||
}
|
||||
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
var validationResult = await automaticUserConfirmationPolicyEnforcementValidator.IsCompliantAsync(
|
||||
new AutomaticUserConfirmationPolicyEnforcementRequest(orgUser.OrganizationId,
|
||||
allOrgUsers,
|
||||
user!));
|
||||
|
||||
var badRequestException = validationResult.Match(
|
||||
error => new BadRequestException(user.Email +
|
||||
" is not compliant with the automatic user confirmation policy: " +
|
||||
error.Message),
|
||||
_ => null);
|
||||
|
||||
if (badRequestException is not null)
|
||||
{
|
||||
throw badRequestException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsTwoFactorRequiredForOrganizationAsync(Guid userId, Guid organizationId)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
@@ -43,7 +45,9 @@ public class CloudOrganizationSignUpCommand(
|
||||
IPushNotificationService pushNotificationService,
|
||||
ICollectionRepository collectionRepository,
|
||||
IDeviceRepository deviceRepository,
|
||||
IPricingClient pricingClient) : ICloudOrganizationSignUpCommand
|
||||
IPricingClient pricingClient,
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IFeatureService featureService) : ICloudOrganizationSignUpCommand
|
||||
{
|
||||
public async Task<SignUpOrganizationResponse> SignUpOrganizationAsync(OrganizationSignup signup)
|
||||
{
|
||||
@@ -237,6 +241,17 @@ public class CloudOrganizationSignUpCommand(
|
||||
|
||||
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
var requirement = await policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerId);
|
||||
|
||||
if (requirement.CannotCreateNewOrganization())
|
||||
{
|
||||
throw new BadRequestException("You may not create an organization. You belong to an organization " +
|
||||
"which has a policy that prohibits you from being a member of any other organization.");
|
||||
}
|
||||
}
|
||||
|
||||
var anySingleOrgPolicies = await policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
||||
if (anySingleOrgPolicies)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
@@ -28,6 +30,8 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
|
||||
public InitPendingOrganizationCommand(
|
||||
IOrganizationService organizationService,
|
||||
@@ -37,7 +41,9 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
IGlobalSettings globalSettings,
|
||||
IPolicyService policyService,
|
||||
IOrganizationUserRepository organizationUserRepository
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IFeatureService featureService,
|
||||
IPolicyRequirementQuery policyRequirementQuery
|
||||
)
|
||||
{
|
||||
_organizationService = organizationService;
|
||||
@@ -48,6 +54,8 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
|
||||
_globalSettings = globalSettings;
|
||||
_policyService = policyService;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_featureService = featureService;
|
||||
_policyRequirementQuery = policyRequirementQuery;
|
||||
}
|
||||
|
||||
public async Task InitPendingOrganizationAsync(User user, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName, string emailToken)
|
||||
@@ -113,6 +121,17 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
|
||||
|
||||
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||
{
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
var requirement = await _policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerId);
|
||||
|
||||
if (requirement.CannotCreateNewOrganization())
|
||||
{
|
||||
throw new BadRequestException("You may not create an organization. You belong to an organization " +
|
||||
"which has a policy that prohibits you from being a member of any other organization.");
|
||||
}
|
||||
}
|
||||
|
||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
||||
if (anySingleOrgPolicies)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
@@ -31,6 +33,8 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||
|
||||
public SelfHostedOrganizationSignUpCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@@ -44,7 +48,9 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
|
||||
ILicensingService licensingService,
|
||||
IPolicyService policyService,
|
||||
IGlobalSettings globalSettings,
|
||||
IStripePaymentService paymentService)
|
||||
IStripePaymentService paymentService,
|
||||
IFeatureService featureService,
|
||||
IPolicyRequirementQuery policyRequirementQuery)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@@ -58,6 +64,8 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
|
||||
_policyService = policyService;
|
||||
_globalSettings = globalSettings;
|
||||
_paymentService = paymentService;
|
||||
_featureService = featureService;
|
||||
_policyRequirementQuery = policyRequirementQuery;
|
||||
}
|
||||
|
||||
public async Task<(Organization organization, OrganizationUser? organizationUser)> SignUpAsync(
|
||||
@@ -103,6 +111,17 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
|
||||
|
||||
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||
{
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
|
||||
{
|
||||
var requirement = await _policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerId);
|
||||
|
||||
if (requirement.CannotCreateNewOrganization())
|
||||
{
|
||||
throw new BadRequestException("You may not create an organization. You belong to an organization " +
|
||||
"which has a policy that prohibits you from being a member of any other organization.");
|
||||
}
|
||||
}
|
||||
|
||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
||||
if (anySingleOrgPolicies)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
|
||||
|
||||
/// <summary>
|
||||
/// Request object for <see cref="AutomaticUserConfirmationPolicyEnforcementValidator"/>
|
||||
/// </summary>
|
||||
public record AutomaticUserConfirmationPolicyEnforcementRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Organization to be validated
|
||||
/// </summary>
|
||||
public Guid OrganizationId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All organization users that match the provided user.
|
||||
/// </summary>
|
||||
public ICollection<OrganizationUser> AllOrganizationUsers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// User associated with the organization user to be confirmed
|
||||
/// </summary>
|
||||
public User User { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Request object for <see cref="AutomaticUserConfirmationPolicyEnforcementValidator"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This record is used to encapsulate the data required for handling the automatic confirmation policy enforcement.
|
||||
/// </remarks>
|
||||
/// <param name="organizationId">The organization to be validated.</param>
|
||||
/// <param name="organizationUsers">All organization users that match the provided user.</param>
|
||||
/// <param name="user">The user entity connecting all org users provided.</param>
|
||||
public AutomaticUserConfirmationPolicyEnforcementRequest(
|
||||
Guid organizationId,
|
||||
IEnumerable<OrganizationUser> organizationUsers,
|
||||
User user)
|
||||
{
|
||||
OrganizationId = organizationId;
|
||||
AllOrganizationUsers = organizationUsers.ToArray();
|
||||
User = user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Utilities.v2.Validation;
|
||||
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
|
||||
|
||||
public class AutomaticUserConfirmationPolicyEnforcementValidator(
|
||||
IPolicyRequirementQuery policyRequirementQuery,
|
||||
IProviderUserRepository providerUserRepository)
|
||||
: IAutomaticUserConfirmationPolicyEnforcementValidator
|
||||
{
|
||||
public async Task<ValidationResult<AutomaticUserConfirmationPolicyEnforcementRequest>> IsCompliantAsync(
|
||||
AutomaticUserConfirmationPolicyEnforcementRequest request)
|
||||
{
|
||||
var automaticUserConfirmationPolicyRequirement = await policyRequirementQuery
|
||||
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(request.User.Id);
|
||||
|
||||
var currentOrganizationUser = request.AllOrganizationUsers
|
||||
.FirstOrDefault(x => x.OrganizationId == request.OrganizationId
|
||||
&& x.UserId == request.User.Id);
|
||||
|
||||
if (currentOrganizationUser is null)
|
||||
{
|
||||
return Invalid(request, new CurrentOrganizationUserIsNotPresentInRequest());
|
||||
}
|
||||
|
||||
if (automaticUserConfirmationPolicyRequirement.IsEnabled(request.OrganizationId))
|
||||
{
|
||||
if ((await providerUserRepository.GetManyByUserAsync(request.User.Id)).Count != 0)
|
||||
{
|
||||
return Invalid(request, new ProviderUsersCannotJoin());
|
||||
}
|
||||
|
||||
if (request.AllOrganizationUsers.Count > 1)
|
||||
{
|
||||
return Invalid(request, new UserCannotBelongToAnotherOrganization());
|
||||
}
|
||||
}
|
||||
|
||||
if (automaticUserConfirmationPolicyRequirement.IsEnabledForOrganizationsOtherThan(currentOrganizationUser.OrganizationId))
|
||||
{
|
||||
return Invalid(request, new OtherOrganizationDoesNotAllowOtherMembership());
|
||||
}
|
||||
|
||||
return Valid(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Utilities.v2.Validation;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
|
||||
|
||||
/// <summary>
|
||||
/// Used to enforce the Automatic User Confirmation policy. It uses the <see cref="IPolicyRequirementQuery"/> to retrieve
|
||||
/// the <see cref="AutomaticUserConfirmationPolicyRequirement"/>. It is used to check to make sure the given user is
|
||||
/// valid for the Automatic User Confirmation policy. It also validates that the given user is not a provider
|
||||
/// or a member of another organization regardless of status or type.
|
||||
/// </summary>
|
||||
public interface IAutomaticUserConfirmationPolicyEnforcementValidator
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given user is compliant with the Automatic User Confirmation policy.
|
||||
///
|
||||
/// To be compliant, a user must
|
||||
/// - not be a member of a provider
|
||||
/// - not be a member of another organization
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <remarks>
|
||||
/// This uses the validation result pattern to avoid throwing exceptions.
|
||||
/// </remarks>
|
||||
/// <returns>A validation result with the error message if applicable.</returns>
|
||||
Task<ValidationResult<AutomaticUserConfirmationPolicyEnforcementRequest>> IsCompliantAsync(AutomaticUserConfirmationPolicyEnforcementRequest request);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the enforcement status of the Automatic User Confirmation policy.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Automatic User Confirmation policy is enforced against all types of users regardless of status or type.
|
||||
///
|
||||
/// Users cannot:
|
||||
/// <ul>
|
||||
/// <li>Be a member of another organization (similar to Single Organization Policy)</li>
|
||||
/// <li>Cannot be a provider</li>
|
||||
/// </ul>
|
||||
/// </remarks>
|
||||
/// <param name="policyDetails">Collection of policy details that apply to this user id</param>
|
||||
public class AutomaticUserConfirmationPolicyRequirement(IEnumerable<PolicyDetails> policyDetails) : IPolicyRequirement
|
||||
{
|
||||
public bool CannotBeGrantedEmergencyAccess() => policyDetails.Any();
|
||||
|
||||
public bool CannotJoinProvider() => policyDetails.Any();
|
||||
|
||||
public bool CannotCreateProvider() => policyDetails.Any();
|
||||
|
||||
public bool CannotCreateNewOrganization() => policyDetails.Any();
|
||||
|
||||
public bool IsEnabled(Guid organizationId) => policyDetails.Any(p => p.OrganizationId == organizationId);
|
||||
|
||||
public bool IsEnabledForOrganizationsOtherThan(Guid organizationId) =>
|
||||
policyDetails.Any(p => p.OrganizationId != organizationId);
|
||||
}
|
||||
|
||||
public class AutomaticUserConfirmationPolicyRequirementFactory : BasePolicyRequirementFactory<AutomaticUserConfirmationPolicyRequirement>
|
||||
{
|
||||
public override PolicyType PolicyType => PolicyType.AutomaticUserConfirmation;
|
||||
|
||||
protected override IEnumerable<OrganizationUserType> ExemptRoles => [];
|
||||
|
||||
protected override IEnumerable<OrganizationUserStatusType> ExemptStatuses => [];
|
||||
|
||||
protected override bool ExemptProviders => false;
|
||||
|
||||
public override AutomaticUserConfirmationPolicyRequirement Create(IEnumerable<PolicyDetails> policyDetails) =>
|
||||
new(policyDetails);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||
@@ -23,6 +24,8 @@ public static class PolicyServiceCollectionExtensions
|
||||
services.AddPolicyRequirements();
|
||||
services.AddPolicySideEffects();
|
||||
services.AddPolicyUpdateEvents();
|
||||
|
||||
services.AddScoped<IAutomaticUserConfirmationPolicyEnforcementValidator, AutomaticUserConfirmationPolicyEnforcementValidator>();
|
||||
}
|
||||
|
||||
[Obsolete("Use AddPolicyUpdateEvents instead.")]
|
||||
@@ -69,5 +72,6 @@ public static class PolicyServiceCollectionExtensions
|
||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, RequireTwoFactorPolicyRequirementFactory>();
|
||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, MasterPasswordPolicyRequirementFactory>();
|
||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, SingleOrganizationPolicyRequirementFactory>();
|
||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, AutomaticUserConfirmationPolicyRequirementFactory>();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user