Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -197,23 +197,7 @@ public async Task<OrganizationUser> AcceptOrgUserAsync(OrganizationUser orgUser,
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 (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.");
}

// Enforce Single Organization Policy of other organizations user is a member of
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id,
PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{
throw new BadRequestException("You cannot join this organization because you are a member of another organization which forbids it");
}
await ValidateSingleOrganizationPolicyAsync(orgUser, allOrgUsers, user);

// Enforce Two Factor Authentication Policy of organization user is trying to join
await ValidateTwoFactorAuthenticationPolicyAsync(user, orgUser.OrganizationId);
Expand All @@ -236,6 +220,39 @@ public async Task<OrganizationUser> AcceptOrgUserAsync(OrganizationUser orgUser,
return orgUser;
}

private async Task ValidateSingleOrganizationPolicyAsync(OrganizationUser orgUser, ICollection<OrganizationUser> allOrgUsers, User user)
{
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
{
var singleOrgRequirement = await _policyRequirementQuery.GetAsync<SingleOrganizationPolicyRequirement>(user.Id);
var error = singleOrgRequirement.CanJoinOrganization(orgUser.OrganizationId, allOrgUsers);
if (error is not null)
{
throw new BadRequestException(error.Message);
}
}
else
{
// Enforce Single Organization Policy of organization user is trying to join
var invitedSingleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
PolicyType.SingleOrg, OrganizationUserStatusType.Invited);

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.");
}

// Enforce Single Organization Policy of other organizations user is a member of
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id,
PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{
throw new BadRequestException("You cannot join this organization because you are a member of another organization which forbids it");
}
}
}

private async Task ValidateTwoFactorAuthenticationPolicyAsync(User user, Guid organizationId)
{
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,20 @@ private async Task CheckPoliciesAsync(Guid organizationId, User user,
}
}

if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
{
var singleOrgRequirement = await _policyRequirementQuery.GetAsync<SingleOrganizationPolicyRequirement>(user.Id);
var error = singleOrgRequirement.CanJoinOrganization(organizationId, userOrgs);
if (error is not null)
{
throw new BadRequestException(error.Message);
}

return;
}

var singleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg);
var otherSingleOrgPolicies =
singleOrgPolicies.Where(p => p.OrganizationId != organizationId);
var otherSingleOrgPolicies = singleOrgPolicies.Where(p => p.OrganizationId != organizationId);
// Enforce Single Organization Policy for this organization
if (hasOtherOrgs && singleOrgPolicies.Any(p => p.OrganizationId == organizationId))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,55 +293,83 @@ private async Task CheckPoliciesBeforeRestoreAsync(OrganizationUser orgUser, boo

var userId = orgUser.UserId.Value;

// Enforce Single Organization Policy of organization user is being restored to
var allOrgUsers = await organizationUserRepository.GetManyByUserAsync(userId);
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
var singleOrgPoliciesApplyingToRevokedUsers = await policyService.GetPoliciesApplicableToUserAsync(userId,
PolicyType.SingleOrg, OrganizationUserStatusType.Revoked);
var singleOrgPolicyApplies =
singleOrgPoliciesApplyingToRevokedUsers.Any(p => p.OrganizationId == orgUser.OrganizationId);

var singleOrgCompliant = true;
var belongsToOtherOrgCompliant = true;
var twoFactorCompliant = true;
var user = await userRepository.GetByIdAsync(userId);

if (hasOtherOrgs && singleOrgPolicyApplies)
if (featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
{
singleOrgCompliant = false;
}
var singleOrgRequirement = await policyRequirementQuery.GetAsync<SingleOrganizationPolicyRequirement>(userId);
var singleOrgError = singleOrgRequirement.CanJoinOrganization(orgUser.OrganizationId, allOrgUsers);

// Enforce Single Organization Policy of other organizations user is a member of
var anySingleOrgPolicies = await policyService.AnyPoliciesApplicableToUserAsync(userId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{
belongsToOtherOrgCompliant = false;
}
var twoFactorCompliant = true;
if (!userHasTwoFactorEnabled)
{
twoFactorCompliant = !await IsTwoFactorRequiredForOrganizationAsync(userId, orgUser.OrganizationId);
}

// Enforce 2FA Policy of organization user is trying to join
if (!userHasTwoFactorEnabled)
{
twoFactorCompliant = !await IsTwoFactorRequiredForOrganizationAsync(userId, orgUser.OrganizationId);
if (singleOrgError is not null && !twoFactorCompliant)
{
throw new BadRequestException(user.Email +
" is not compliant with the single organization and two-step login policy");
}
else if (singleOrgError is not null)
{
throw new BadRequestException(user.Email + " is not compliant with the single organization policy");
}
else if (!twoFactorCompliant)
{
throw new BadRequestException(user.Email + " is not compliant with the two-step login policy");
}
}
else
{
// Enforce Single Organization Policy of organization user is being restored to
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
var singleOrgPoliciesApplyingToRevokedUsers = await policyService.GetPoliciesApplicableToUserAsync(userId,
PolicyType.SingleOrg, OrganizationUserStatusType.Revoked);
var singleOrgPolicyApplies =
singleOrgPoliciesApplyingToRevokedUsers.Any(p => p.OrganizationId == orgUser.OrganizationId);

var singleOrgCompliant = true;
var belongsToOtherOrgCompliant = true;
var twoFactorCompliant = true;

if (hasOtherOrgs && singleOrgPolicyApplies)
{
singleOrgCompliant = false;
}

var user = await userRepository.GetByIdAsync(userId);
// Enforce Single Organization Policy of other organizations user is a member of
var anySingleOrgPolicies = await policyService.AnyPoliciesApplicableToUserAsync(userId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{
belongsToOtherOrgCompliant = false;
}

if (!singleOrgCompliant && !twoFactorCompliant)
{
throw new BadRequestException(user.Email +
" is not compliant with the single organization and two-step login policy");
}
else if (!singleOrgCompliant)
{
throw new BadRequestException(user.Email + " is not compliant with the single organization policy");
}
else if (!belongsToOtherOrgCompliant)
{
throw new BadRequestException(user.Email +
" belongs to an organization that doesn't allow them to join multiple organizations");
}
else if (!twoFactorCompliant)
{
throw new BadRequestException(user.Email + " is not compliant with the two-step login policy");
// Enforce 2FA Policy of organization user is trying to join
if (!userHasTwoFactorEnabled)
{
twoFactorCompliant = !await IsTwoFactorRequiredForOrganizationAsync(userId, orgUser.OrganizationId);
}

if (!singleOrgCompliant && !twoFactorCompliant)
{
throw new BadRequestException(user.Email +
" is not compliant with the single organization and two-step login policy");
}
else if (!singleOrgCompliant)
{
throw new BadRequestException(user.Email + " is not compliant with the single organization policy");
}
else if (!belongsToOtherOrgCompliant)
{
throw new BadRequestException(user.Email +
" belongs to an organization that doesn't allow them to join multiple organizations");
}
else if (!twoFactorCompliant)
{
throw new BadRequestException(user.Email + " is not compliant with the two-step login policy");
}
}

if (featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
}
}

if (featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
{
var singleOrgRequirement = await policyRequirementQuery.GetAsync<SingleOrganizationPolicyRequirement>(ownerId);
var error = singleOrgRequirement.CanCreateOrganization();
if (error is not null)
{
throw new BadRequestException(error.Message);
}

return;
}

var anySingleOrgPolicies = await policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
}
}

if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
{
var singleOrgRequirement = await _policyRequirementQuery.GetAsync<SingleOrganizationPolicyRequirement>(ownerId);
var error = singleOrgRequirement.CanCreateOrganization();
if (error is not null)
{
throw new BadRequestException(error.Message);
}

return;
}

var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
}
}

if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
{
var singleOrgRequirement = await _policyRequirementQuery.GetAsync<SingleOrganizationPolicyRequirement>(ownerId);
var error = singleOrgRequirement.CanCreateOrganization();
if (error is not null)
{
throw new BadRequestException(error.Message);
}

return;
}

var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,90 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.Utilities.v2;
using Bit.Core.Entities;
using Bit.Core.Enums;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public class SingleOrganizationPolicyRequirement(IEnumerable<PolicyDetails> policyDetails) : IPolicyRequirement
{
public bool IsSingleOrgEnabledForThisOrganization(Guid organizationId) =>
policyDetails.Any(p => p.OrganizationId == organizationId);
public record UserIsAMemberOfAnOrganizationThatHasSingleOrgPolicy() : BadRequestError(
"Member cannot join the organization because they are in another organization which forbids it.");

public bool IsSingleOrgEnabledForOrganizationsOtherThan(Guid organizationId) =>
policyDetails.Any(p => p.OrganizationId != organizationId);
public record UserIsAMemberOfAnotherOrganization()
: BadRequestError("Member cannot join the organization until they leave or remove all other organizations.");

public record UserCannotCreateOrg()
: BadRequestError(
"Cannot create organization because single organization policy is enabled for another organization.");

/// <summary>
/// Returns an error if the user cannot create an organization due to being a part of another organization.
/// </summary>
/// <returns>UserCannotCreateOrg error if the user cannot create an organization, otherwise null.</returns>
public Error? CanCreateOrganization() => policyDetails
.Any(p => p.HasStatus([OrganizationUserStatusType.Accepted, OrganizationUserStatusType.Confirmed]))
? new UserCannotCreateOrg()
: null;

/// <summary>
/// Returns an error if the user cannot join the organization.
/// </summary>
/// <param name="organizationId">Organization the user is attempting to join.</param>
/// <param name="allOrgUsers">All organization users that a given user is linked to.</param>
/// <returns>
/// UserIsAMemberOfAnotherOrganization or UserIsAMemberOfAnOrganizationThatHasSingleOrgPolicy if the user cannot
/// join the organization, otherwise null.
/// </returns>
public Error? CanJoinOrganization(Guid organizationId, ICollection<OrganizationUser> allOrgUsers) =>
IsCompliantWithTargetOrganization(organizationId, allOrgUsers)
?? IsEnabledForOtherOrganizationsUserIsAPartOf(organizationId);

/// <summary>
/// Returns true if the policy is enabled for the target organization.
/// </summary>
/// <param name="targetOrganizationId">Organization Id the user is attempting to join</param>
/// <returns></returns>
public bool IsEnabledForTargetOrganization(Guid targetOrganizationId) =>
policyDetails.Any(p => p.OrganizationId == targetOrganizationId);

/// <summary>
/// Will return an error if the user is a member of another organization and Single Organization is enabled for the
/// target organization.
/// </summary>
/// <param name="targetOrganizationId">Organization Id the user is attempting to join</param>
/// <param name="allOrgUsers">All organization users associated with the user id</param>
/// <returns>
/// UserIsAMemberOfAnotherOrganization if the user cannot join the target organization, otherwise null.
/// </returns>
public Error? IsCompliantWithTargetOrganization(Guid targetOrganizationId,
ICollection<OrganizationUser> allOrgUsers) =>
IsEnabledForTargetOrganization(targetOrganizationId)
&& allOrgUsers.Any(ou => ou.OrganizationId != targetOrganizationId)
? new UserIsAMemberOfAnotherOrganization()
: null;

/// <summary>
/// Returns an error if the user is a member of another organization that has enabled the Single Organization policy.
/// </summary>
/// <param name="targetOrganizationId">Organization Id the user is attempting to join</param>
/// <returns>
/// UserIsAMemberOfAnOrganizationThatHasSingleOrgPolicy if the user is a member of another organization that has
/// enabled the Single Organization policy, otherwise null.
/// </returns>
public Error? IsEnabledForOtherOrganizationsUserIsAPartOf(Guid targetOrganizationId) =>
policyDetails.Any(p => p.OrganizationId != targetOrganizationId
&& p.HasStatus([OrganizationUserStatusType.Accepted, OrganizationUserStatusType.Confirmed]))
? new UserIsAMemberOfAnOrganizationThatHasSingleOrgPolicy()
: null;
}

public class SingleOrganizationPolicyRequirementFactory : BasePolicyRequirementFactory<SingleOrganizationPolicyRequirement>
{
public override PolicyType PolicyType => PolicyType.SingleOrg;

protected override IEnumerable<OrganizationUserStatusType> ExemptStatuses { get; } = [];

public override SingleOrganizationPolicyRequirement Create(IEnumerable<PolicyDetails> policyDetails) =>
new(policyDetails);
}
Loading
Loading