Skip to content

Commit 1dcbf27

Browse files
Add revocation reasons (#7473)
1 parent 471496c commit 1dcbf27

27 files changed

Lines changed: 111 additions & 76 deletions

File tree

bitwarden_license/src/Scim/Controllers/v2/UsersController.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ public async Task<IActionResult> Put(Guid organizationId, Guid id, [FromBody] Sc
106106
new RevokeOrganizationUsersRequest(
107107
organizationId,
108108
[id],
109-
new SystemUser(EventSystemUser.SCIM)));
109+
new SystemUser(EventSystemUser.SCIM),
110+
RevocationReason.Manual));
110111

111112
var errors = results.Select(x => x.Result.Match(
112113
y => $"{y.Message} for user {x.Id}",

bitwarden_license/src/Scim/Users/PatchUserCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private async Task<bool> HandleActiveOperationAsync(Core.Entities.OrganizationUs
107107
}
108108
else if (!active && orgUser.Status != OrganizationUserStatusType.Revoked)
109109
{
110-
await _revokeOrganizationUserCommand.RevokeUserAsync(orgUser, EventSystemUser.SCIM);
110+
await _revokeOrganizationUserCommand.RevokeUserAsync(orgUser, EventSystemUser.SCIM, RevocationReason.Manual);
111111
return true;
112112
}
113113
return false;

bitwarden_license/test/Scim.Test/Users/PatchUserCommandTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public async Task PatchUser_RevokePath_Success(SutProvider<PatchUserCommand> sut
102102

103103
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
104104

105-
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().Received(1).RevokeUserAsync(organizationUser, EventSystemUser.SCIM);
105+
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().Received(1).RevokeUserAsync(organizationUser, EventSystemUser.SCIM, RevocationReason.Manual);
106106
}
107107

108108
[Theory]
@@ -130,7 +130,7 @@ public async Task PatchUser_RevokeValue_Success(SutProvider<PatchUserCommand> su
130130

131131
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
132132

133-
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().Received(1).RevokeUserAsync(organizationUser, EventSystemUser.SCIM);
133+
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().Received(1).RevokeUserAsync(organizationUser, EventSystemUser.SCIM, RevocationReason.Manual);
134134
}
135135

136136
[Theory]
@@ -150,7 +150,7 @@ public async Task PatchUser_NoAction_Success(SutProvider<PatchUserCommand> sutPr
150150
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
151151

152152
await sutProvider.GetDependency<IRestoreOrganizationUserCommand>().DidNotReceiveWithAnyArgs().RestoreUserAsync(default, EventSystemUser.SCIM);
153-
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().DidNotReceiveWithAnyArgs().RevokeUserAsync(default, EventSystemUser.SCIM);
153+
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().DidNotReceiveWithAnyArgs().RevokeUserAsync(default, EventSystemUser.SCIM, default);
154154
}
155155

156156
[Theory]
@@ -380,7 +380,7 @@ public async Task PatchUser_UnsupportedOperation_LogsWarningAndSucceeds(SutProvi
380380

381381
// Verify no restore or revoke operations were called
382382
await sutProvider.GetDependency<IRestoreOrganizationUserCommand>().DidNotReceiveWithAnyArgs().RestoreUserAsync(default, EventSystemUser.SCIM);
383-
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().DidNotReceiveWithAnyArgs().RevokeUserAsync(default, EventSystemUser.SCIM);
383+
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().DidNotReceiveWithAnyArgs().RevokeUserAsync(default, EventSystemUser.SCIM, default);
384384
}
385385

386386
[Theory]
@@ -415,7 +415,7 @@ public async Task PatchUser_ActiveAndExternalIdFromValue_Success(SutProvider<Pat
415415
await sutProvider.Sut.PatchUserAsync(organizationUser.OrganizationId, organizationUser.Id, scimPatchModel);
416416

417417
// Verify both operations were processed
418-
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().Received(1).RevokeUserAsync(organizationUser, EventSystemUser.SCIM);
418+
await sutProvider.GetDependency<IRevokeOrganizationUserCommand>().Received(1).RevokeUserAsync(organizationUser, EventSystemUser.SCIM, RevocationReason.Manual);
419419
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).ReplaceAsync(
420420
Arg.Is<OrganizationUser>(ou => ou.ExternalId == newExternalId));
421421
}

src/Api/AdminConsole/Controllers/OrganizationUsersController.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> PostBulk
644644
[Authorize<ManageUsersRequirement>]
645645
public async Task RevokeAsync(Guid orgId, Guid id)
646646
{
647-
await RestoreOrRevokeUserAsync(orgId, id, _revokeOrganizationUserCommand.RevokeUserAsync);
647+
await RestoreOrRevokeUserAsync(orgId, id, (orgUser, userId) => _revokeOrganizationUserCommand.RevokeUserAsync(orgUser, userId, RevocationReason.Manual));
648648
}
649649

650650
[HttpPut("revoke-self")]
@@ -683,7 +683,8 @@ public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRevo
683683
new V2_RevokeOrganizationUserCommand.RevokeOrganizationUsersRequest(
684684
orgId,
685685
model.Ids.ToArray(),
686-
new StandardUser(currentUserId.Value, await _currentContext.OrganizationOwner(orgId))));
686+
new StandardUser(currentUserId.Value, await _currentContext.OrganizationOwner(orgId)),
687+
RevocationReason.Manual));
687688

688689
return new ListResponseModel<OrganizationUserBulkResponseModel>(results
689690
.Select(result => new OrganizationUserBulkResponseModel(result.Id,

src/Api/AdminConsole/Public/Controllers/MembersController.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,8 @@ public async Task<IActionResult> Revoke(Guid id)
339339
var request = new RevokeOrganizationUsersRequest(
340340
_currentContext.OrganizationId!.Value,
341341
[id],
342-
new SystemUser(EventSystemUser.PublicApi)
342+
new SystemUser(EventSystemUser.PublicApi),
343+
RevocationReason.Manual
343344
);
344345

345346
var results = await _revokeOrganizationUserCommandV2.RevokeUsersAsync(request);

src/Core/AdminConsole/OrganizationFeatures/AccountRecovery/v2/AdminRecoverAccountCommand.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ await revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUs
125125
new RevokeOrganizationUsersRequest(
126126
o.OrganizationId,
127127
[new OrganizationUserUserDetails { Id = o.OrganizationUserId, OrganizationId = o.OrganizationId }],
128-
new SystemUser(EventSystemUser.TwoFactorDisabled)));
128+
new SystemUser(EventSystemUser.TwoFactorDisabled),
129+
RevocationReason.TwoFactorPolicyNonCompliance));
129130
await mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), user.Email);
130131
}).ToArray();
131132

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
using Bit.Core.AdminConsole.Models.Data;
2+
using Bit.Core.Enums;
23
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
34

45
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
56

67
public record RevokeOrganizationUsersRequest(
78
Guid OrganizationId,
89
IEnumerable<OrganizationUserUserDetails> OrganizationUsers,
9-
IActingUser ActionPerformedBy)
10+
IActingUser ActionPerformedBy,
11+
RevocationReason RevocationReason)
1012
{
11-
public RevokeOrganizationUsersRequest(Guid organizationId, OrganizationUserUserDetails organizationUser, IActingUser actionPerformedBy)
12-
: this(organizationId, [organizationUser], actionPerformedBy) { }
13+
public RevokeOrganizationUsersRequest(Guid organizationId, OrganizationUserUserDetails organizationUser, IActingUser actionPerformedBy, RevocationReason revocationReason)
14+
: this(organizationId, [organizationUser], actionPerformedBy, revocationReason) { }
1315
}

src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeNonCompliantOrganizationUserCommand.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,42 @@ public async Task<CommandResult> RevokeNonCompliantOrganizationUsersAsync(Revoke
3030
return validationResult;
3131
}
3232

33-
await organizationUserRepository.RevokeManyAsync(request.OrganizationUsers.Select(x => x.Id));
33+
await organizationUserRepository.RevokeManyAsync(request.OrganizationUsers.Select(x => x.Id), request.RevocationReason);
3434

3535
var now = timeProvider.GetUtcNow();
3636

37+
var eventType = MapRevocationReasonToEventType(request.RevocationReason);
38+
3739
switch (request.ActionPerformedBy)
3840
{
3941
case StandardUser:
4042
await eventService.LogOrganizationUserEventsAsync(
41-
request.OrganizationUsers.Select(x => GetRevokedUserEventTuple(x, now)));
43+
request.OrganizationUsers.Select(x => GetRevokedUserEventTuple(x, eventType, now)));
4244
break;
4345
case SystemUser { SystemUserType: not null } loggableSystem:
4446
await eventService.LogOrganizationUserEventsAsync(
4547
request.OrganizationUsers.Select(x =>
46-
GetRevokedUserEventBySystemUserTuple(x, loggableSystem.SystemUserType.Value, now)));
48+
GetRevokedUserEventBySystemUserTuple(x, eventType, loggableSystem.SystemUserType.Value, now)));
4749
break;
4850
}
4951

5052
return validationResult;
5153
}
5254

55+
private static EventType MapRevocationReasonToEventType(RevocationReason reason) => reason switch
56+
{
57+
RevocationReason.TwoFactorPolicyNonCompliance => EventType.OrganizationUser_Revoked_TwoFactorNonCompliance,
58+
RevocationReason.SingleOrgPolicyNonCompliance => EventType.OrganizationUser_Revoked_SingleOrganizationNonCompliance,
59+
_ => EventType.OrganizationUser_Revoked
60+
};
61+
5362
private static (OrganizationUserUserDetails organizationUser, EventType eventType, DateTime? time) GetRevokedUserEventTuple(
54-
OrganizationUserUserDetails organizationUser, DateTimeOffset dateTimeOffset) =>
55-
new(organizationUser, EventType.OrganizationUser_Revoked, dateTimeOffset.UtcDateTime);
63+
OrganizationUserUserDetails organizationUser, EventType eventType, DateTimeOffset dateTimeOffset) =>
64+
new(organizationUser, eventType, dateTimeOffset.UtcDateTime);
5665

5766
private static (OrganizationUserUserDetails organizationUser, EventType eventType, EventSystemUser eventSystemUser, DateTime? time) GetRevokedUserEventBySystemUserTuple(
58-
OrganizationUserUserDetails organizationUser, EventSystemUser systemUser, DateTimeOffset dateTimeOffset) => new(organizationUser,
59-
EventType.OrganizationUser_Revoked, systemUser, dateTimeOffset.UtcDateTime);
67+
OrganizationUserUserDetails organizationUser, EventType eventType, EventSystemUser systemUser, DateTimeOffset dateTimeOffset) => new(organizationUser,
68+
eventType, systemUser, dateTimeOffset.UtcDateTime);
6069

6170
private async Task<CommandResult> ValidateAsync(RevokeOrganizationUsersRequest request)
6271
{

src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/IRevokeOrganizationUserCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RevokeUse
55

66
public interface IRevokeOrganizationUserCommand
77
{
8-
Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId);
9-
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser);
8+
Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId, RevocationReason reason);
9+
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser, RevocationReason reason);
1010
}

src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/RevokeOrganizationUserCommand.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class RevokeOrganizationUserCommand(
1717
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery)
1818
: IRevokeOrganizationUserCommand
1919
{
20-
public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId)
20+
public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId, RevocationReason reason)
2121
{
2222
if (revokingUserId.HasValue && organizationUser.UserId == revokingUserId.Value)
2323
{
@@ -30,7 +30,7 @@ public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revok
3030
throw new BadRequestException("Only owners can revoke other owners.");
3131
}
3232

33-
await RepositoryRevokeUserAsync(organizationUser);
33+
await RepositoryRevokeUserAsync(organizationUser, reason);
3434
await eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked);
3535

3636
if (organizationUser.UserId.HasValue)
@@ -40,9 +40,9 @@ public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revok
4040
}
4141

4242
public async Task RevokeUserAsync(OrganizationUser organizationUser,
43-
EventSystemUser systemUser)
43+
EventSystemUser systemUser, RevocationReason reason)
4444
{
45-
await RepositoryRevokeUserAsync(organizationUser);
45+
await RepositoryRevokeUserAsync(organizationUser, reason);
4646
await eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked,
4747
systemUser);
4848

@@ -52,7 +52,7 @@ await eventService.LogOrganizationUserEventAsync(organizationUser, EventType.Org
5252
}
5353
}
5454

55-
private async Task RepositoryRevokeUserAsync(OrganizationUser organizationUser)
55+
private async Task RepositoryRevokeUserAsync(OrganizationUser organizationUser, RevocationReason reason)
5656
{
5757
if (organizationUser.Status == OrganizationUserStatusType.Revoked)
5858
{
@@ -65,7 +65,8 @@ private async Task RepositoryRevokeUserAsync(OrganizationUser organizationUser)
6565
throw new BadRequestException("Organization must have at least one confirmed owner.");
6666
}
6767

68-
await organizationUserRepository.RevokeAsync(organizationUser.Id);
68+
await organizationUserRepository.RevokeAsync(organizationUser.Id, reason);
6969
organizationUser.Status = OrganizationUserStatusType.Revoked;
70+
organizationUser.RevocationReason = reason;
7071
}
7172
}

0 commit comments

Comments
 (0)