Skip to content

Commit e0a0871

Browse files
feat(emergency-access): [PM-29585] Prevent New EA Invitations or Acceptance (#6940)
* feat(emergency-access): [PM-29585] Prevent New EA Invitations or Acceptance - Initial implementation * fix(emergency-access): [PM-29585] Prevent New EA Invitations or Acceptance - Changes in a good place. Need to write tests. * test(emergency-access): [PM-29585] Prevent New EA Invitations or Acceptance - Service tests have been added. * fix(emergency-access): [PM-29585] Prevent New EA Invitations or Acceptance - Fixed comment.
1 parent 20d94c3 commit e0a0871

4 files changed

Lines changed: 372 additions & 4 deletions

File tree

src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/AutomaticUserConfirmationPolicyRequirement.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,25 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements
1919
/// <param name="policyDetails">Collection of policy details that apply to this user id</param>
2020
public class AutomaticUserConfirmationPolicyRequirement(IEnumerable<PolicyDetails> policyDetails) : IPolicyRequirement
2121
{
22-
public bool CannotHaveEmergencyAccess() => policyDetails.Any();
22+
/// <summary>
23+
/// Returns true if the user cannot invite to emergency access because they are in an
24+
/// auto-confirm organization with status Accepted, Confirmed, or Revoked.
25+
/// </summary>
26+
public bool GrantorCannotInviteToEmergencyAccess() => policyDetails.Any(p =>
27+
p.OrganizationUserStatus is
28+
OrganizationUserStatusType.Accepted or
29+
OrganizationUserStatusType.Confirmed or
30+
OrganizationUserStatusType.Revoked);
31+
32+
/// <summary>
33+
/// Returns true if the user cannot accept emergency access because they are in an
34+
/// auto-confirm organization with status Accepted, Confirmed, or Revoked.
35+
/// </summary>
36+
public bool GranteeCannotAcceptEmergencyAccess() => policyDetails.Any(p =>
37+
p.OrganizationUserStatus is
38+
OrganizationUserStatusType.Accepted or
39+
OrganizationUserStatusType.Confirmed or
40+
OrganizationUserStatusType.Revoked);
2341

2442
public bool CannotJoinProvider() => policyDetails.Any();
2543

src/Core/Auth/UserFeatures/EmergencyAccess/EmergencyAccessService.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using Bit.Core.AdminConsole.Entities;
55
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
6+
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
7+
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
68
using Bit.Core.AdminConsole.Repositories;
79
using Bit.Core.Auth.Enums;
810
using Bit.Core.Auth.Models.Business.Tokenables;
@@ -33,6 +35,8 @@ public class EmergencyAccessService : IEmergencyAccessService
3335
private readonly GlobalSettings _globalSettings;
3436
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer;
3537
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
38+
private readonly IFeatureService _featureService;
39+
private readonly IPolicyRequirementQuery _policyRequirementQuery;
3640

3741
public EmergencyAccessService(
3842
IEmergencyAccessRepository emergencyAccessRepository,
@@ -45,7 +49,9 @@ public EmergencyAccessService(
4549
IUserService userService,
4650
GlobalSettings globalSettings,
4751
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer,
48-
IRemoveOrganizationUserCommand removeOrganizationUserCommand)
52+
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
53+
IFeatureService featureService,
54+
IPolicyRequirementQuery policyRequirementQuery)
4955
{
5056
_emergencyAccessRepository = emergencyAccessRepository;
5157
_organizationUserRepository = organizationUserRepository;
@@ -58,6 +64,8 @@ public EmergencyAccessService(
5864
_globalSettings = globalSettings;
5965
_dataProtectorTokenizer = dataProtectorTokenizer;
6066
_removeOrganizationUserCommand = removeOrganizationUserCommand;
67+
_featureService = featureService;
68+
_policyRequirementQuery = policyRequirementQuery;
6169
}
6270

6371
public async Task<Entities.EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime)
@@ -72,6 +80,17 @@ public EmergencyAccessService(
7280
throw new BadRequestException("You cannot use Emergency Access Takeover because you are using Key Connector.");
7381
}
7482

83+
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
84+
{
85+
var requirement = await _policyRequirementQuery
86+
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(grantorUser.Id);
87+
88+
if (requirement.GrantorCannotInviteToEmergencyAccess())
89+
{
90+
throw new BadRequestException("You cannot invite emergency contacts because you are a member of an organization that uses Automatic User Confirmation.");
91+
}
92+
}
93+
7594
var emergencyAccess = new Entities.EmergencyAccess
7695
{
7796
GrantorId = grantorUser.Id,
@@ -130,6 +149,17 @@ public async Task ResendInviteAsync(User grantorUser, Guid emergencyAccessId)
130149
throw new BadRequestException("Invalid token.");
131150
}
132151

152+
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
153+
{
154+
var requirement = await _policyRequirementQuery
155+
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(granteeUser.Id);
156+
157+
if (requirement.GranteeCannotAcceptEmergencyAccess())
158+
{
159+
throw new BadRequestException("You cannot accept emergency access invitations because you are a member of an organization that uses Automatic User Confirmation.");
160+
}
161+
}
162+
133163
if (emergencyAccess.Status == EmergencyAccessStatusType.Accepted)
134164
{
135165
throw new BadRequestException("Invitation already accepted. You will receive an email when the grantor confirms you as an emergency access contact.");
@@ -161,7 +191,7 @@ public async Task ResendInviteAsync(User grantorUser, Guid emergencyAccessId)
161191
return emergencyAccess;
162192
}
163193

164-
// TODO: remove with PM-31327 when we migrate to the command.
194+
// TODO: remove with PM-31327 when we migrate to the command.
165195
public async Task DeleteAsync(Guid emergencyAccessId, Guid userId)
166196
{
167197
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using Bit.Core.AdminConsole.Enums;
2+
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
3+
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
4+
using Bit.Core.Enums;
5+
using Xunit;
6+
7+
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
8+
9+
public class AutomaticUserConfirmationPolicyRequirementTests
10+
{
11+
[Theory]
12+
[InlineData(OrganizationUserStatusType.Accepted)]
13+
[InlineData(OrganizationUserStatusType.Confirmed)]
14+
[InlineData(OrganizationUserStatusType.Revoked)]
15+
public void CannotGrantEmergencyAccess_WithActiveStatus_ReturnsTrue(OrganizationUserStatusType status)
16+
{
17+
var policyDetails = new[]
18+
{
19+
new PolicyDetails
20+
{
21+
OrganizationId = Guid.NewGuid(),
22+
PolicyType = PolicyType.AutomaticUserConfirmation,
23+
OrganizationUserStatus = status
24+
}
25+
};
26+
27+
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
28+
29+
Assert.True(sut.GrantorCannotInviteToEmergencyAccess());
30+
}
31+
32+
[Fact]
33+
public void CannotGrantEmergencyAccess_WithInvitedStatus_ReturnsFalse()
34+
{
35+
var policyDetails = new[]
36+
{
37+
new PolicyDetails
38+
{
39+
OrganizationId = Guid.NewGuid(),
40+
PolicyType = PolicyType.AutomaticUserConfirmation,
41+
OrganizationUserStatus = OrganizationUserStatusType.Invited
42+
}
43+
};
44+
45+
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
46+
47+
Assert.False(sut.GrantorCannotInviteToEmergencyAccess());
48+
}
49+
50+
[Fact]
51+
public void CannotGrantEmergencyAccess_WithNoPolicies_ReturnsFalse()
52+
{
53+
var sut = new AutomaticUserConfirmationPolicyRequirement([]);
54+
55+
Assert.False(sut.GrantorCannotInviteToEmergencyAccess());
56+
}
57+
58+
[Theory]
59+
[InlineData(OrganizationUserStatusType.Accepted)]
60+
[InlineData(OrganizationUserStatusType.Confirmed)]
61+
[InlineData(OrganizationUserStatusType.Revoked)]
62+
public void CannotBeGrantedEmergencyAccess_WithActiveStatus_ReturnsTrue(OrganizationUserStatusType status)
63+
{
64+
var policyDetails = new[]
65+
{
66+
new PolicyDetails
67+
{
68+
OrganizationId = Guid.NewGuid(),
69+
PolicyType = PolicyType.AutomaticUserConfirmation,
70+
OrganizationUserStatus = status
71+
}
72+
};
73+
74+
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
75+
76+
Assert.True(sut.GranteeCannotAcceptEmergencyAccess());
77+
}
78+
79+
[Fact]
80+
public void CannotBeGrantedEmergencyAccess_WithInvitedStatus_ReturnsFalse()
81+
{
82+
var policyDetails = new[]
83+
{
84+
new PolicyDetails
85+
{
86+
OrganizationId = Guid.NewGuid(),
87+
PolicyType = PolicyType.AutomaticUserConfirmation,
88+
OrganizationUserStatus = OrganizationUserStatusType.Invited
89+
}
90+
};
91+
92+
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
93+
94+
Assert.False(sut.GranteeCannotAcceptEmergencyAccess());
95+
}
96+
97+
[Fact]
98+
public void CannotBeGrantedEmergencyAccess_WithNoPolicies_ReturnsFalse()
99+
{
100+
var sut = new AutomaticUserConfirmationPolicyRequirement([]);
101+
102+
Assert.False(sut.GranteeCannotAcceptEmergencyAccess());
103+
}
104+
105+
[Fact]
106+
public void CannotGrantEmergencyAccess_WithMultiplePolicies_OneActive_ReturnsTrue()
107+
{
108+
var policyDetails = new[]
109+
{
110+
new PolicyDetails
111+
{
112+
OrganizationId = Guid.NewGuid(),
113+
PolicyType = PolicyType.AutomaticUserConfirmation,
114+
OrganizationUserStatus = OrganizationUserStatusType.Invited
115+
},
116+
new PolicyDetails
117+
{
118+
OrganizationId = Guid.NewGuid(),
119+
PolicyType = PolicyType.AutomaticUserConfirmation,
120+
OrganizationUserStatus = OrganizationUserStatusType.Confirmed
121+
}
122+
};
123+
124+
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
125+
126+
Assert.True(sut.GrantorCannotInviteToEmergencyAccess());
127+
}
128+
129+
[Fact]
130+
public void CannotBeGrantedEmergencyAccess_WithMultiplePolicies_OneActive_ReturnsTrue()
131+
{
132+
var policyDetails = new[]
133+
{
134+
new PolicyDetails
135+
{
136+
OrganizationId = Guid.NewGuid(),
137+
PolicyType = PolicyType.AutomaticUserConfirmation,
138+
OrganizationUserStatus = OrganizationUserStatusType.Invited
139+
},
140+
new PolicyDetails
141+
{
142+
OrganizationId = Guid.NewGuid(),
143+
PolicyType = PolicyType.AutomaticUserConfirmation,
144+
OrganizationUserStatus = OrganizationUserStatusType.Confirmed
145+
}
146+
};
147+
148+
var sut = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
149+
150+
Assert.True(sut.GranteeCannotAcceptEmergencyAccess());
151+
}
152+
}

0 commit comments

Comments
 (0)