-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PM-10319] - Revoke Non Complaint Users for 2FA and Single Org Policy Enablement #5037
base: main
Are you sure you want to change the base?
Conversation
# Conflicts: # src/Api/AdminConsole/Models/Response/Organizations/CommandResult.cs # src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs
New Issues
Fixed Issues
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, left some suggestions
src/Sql/dbo/Stored Procedures/OrganizationUser_SetStatusForUsersById.sql
Outdated
Show resolved
Hide resolved
src/Sql/dbo/Stored Procedures/OrganizationUser_SetStatusForUsersById.sql
Outdated
Show resolved
Hide resolved
src/Core/AdminConsole/OrganizationFeatures/Shared/CommandResult.cs
Outdated
Show resolved
Hide resolved
...nConsole/OrganizationFeatures/OrganizationUsers/RevokeNonCompliantOrganizationUserCommand.cs
Outdated
Show resolved
Hide resolved
src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Requests/RevokeOrganizationUser.cs
Outdated
Show resolved
Hide resolved
src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Requests/RevokeOrganizationUser.cs
Outdated
Show resolved
Hide resolved
…evokeNonCompliantOrganizationUserCommand.cs Co-authored-by: Rui Tomé <[email protected]>
…ation. Adding unknown type to event system user. Adding optional parameter to SaveAsync in policy service in order to pass in event system user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything in here that is specific to users being revoked for non-compliance with an org policy?
Users can also be revoked for other reasons (including because you looked at an admin funny). If this command is a bit more general, it can be used in all cases and can replace the other (various, confusing) OrganizationService implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could, but I would want to do that change in a separate PR. I also don't want to change the name until then so we know its safe to be the de facto RevokeOrganizationUserCommand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's cool, can you please make a follow-up tech debt ticket to look into this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs
Outdated
Show resolved
Hide resolved
if (commandResult.HasErrors) | ||
{ | ||
await _removeOrganizationUserCommand.RemoveUserAsync(organizationId, orgUser.Id, | ||
savingUserId); | ||
throw new BadRequestException(string.Join(", ", commandResult.ErrorMessages)); | ||
} | ||
|
||
await _mailService.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync( | ||
org.DisplayName(), orgUser.Email); | ||
await Task.WhenAll(revocableUsers.Select(x => | ||
_mailService.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(org.DisplayName(), x.Email))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command does not allow a mixed success / fail state, right? I was initially concerned that if any member could not be revoked, then nobody gets their email. But looking at the command, it validates everyone upfront and then doesn't proceed if there are any errors, so I think that's OK.
That said, if an admin revokes multiple users from the UI, we do allow a success or failure state per user, so we may need to accommodate both behaviors if the command is expanded to replace the OrganizationService methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The complicated thing about this command is all the validation logic. What we could do is break out the validation into a validator class, then have two commands (one for the 2FA/Single Org policy enablement and one for a user explicitly revoking a group of users). Then each command can make the decision about enforcing the validation result.
Unless we just want the command to just only reject invalid responses. Then we would make a Response object to extend CommandResult. Maybe something like
public class RevokeOrganizationUserCommandResponse : CommandResult
{
...
public Guid[] SuccessfullyRevokedUsers { get; }
public Guid[] FailedToRevokeUsers { get; }
...
}
That way we should show the partial success of the action.
...ole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs
Show resolved
Hide resolved
src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs
Outdated
Show resolved
Hide resolved
src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs
Outdated
Show resolved
Hide resolved
...AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidatorTests.cs
Outdated
Show resolved
Hide resolved
...rganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs
Outdated
Show resolved
Hide resolved
var result = await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync( | ||
new RevokeOrganizationUsersRequest(organizationId, revocableOrgUsers, | ||
new StandardUser(savingUserId ?? Guid.Empty, | ||
await _currentContext.OrganizationOwner(organizationId)))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here's a fun one. currentContext.OrganizationOwner
implicitly includes providers. This is... somewhat known when dealing with currentContext
. But I recommend avoiding this ambiguity in your new object - e.g. adding an xmldoc noting that IsOwner
can include providers in this context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do you one better. I'll change it to IsOwnerOrProvider
|
||
|
||
public async Task<OrganizationDomain> UserVerifyOrganizationDomainAsync(OrganizationDomain organizationDomain) | ||
{ | ||
var domainVerificationResult = await VerifyOrganizationDomainAsync(organizationDomain); | ||
var actingUser = new StandardUser(currentContext.UserId ?? Guid.Empty, await currentContext.OrganizationOwner(organizationDomain.OrganizationId)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why pass an empty Guid and not the nullable Guid in the first parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have null. I could throw at this point instead of using Guid.Empty. I was just defaulting to Guid.Empty in the event that a caller hasn't initialized the CurrentContext.
Do we know which applications would call this method without the CurrentContext populated with a user?
src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Requests/RevokeOrganizationUser.cs
Outdated
Show resolved
Hide resolved
@@ -126,4 +127,69 @@ await sutProvider.GetDependency<IMailService>() | |||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(), | |||
"[email protected]"); | |||
} | |||
|
|||
[Theory, BitAutoData] | |||
public async Task OnSaveSideEffectsAsync_GivenUpdatedSingleOrgPolicy_WhenAccountDeprovisioningIsDisabled_Then( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would either rename this to something clearer like OnSaveSideEffectsAsync_WhenAccountDeprovisioningDisabled_DoesNotRevokeUsers
or I would update the test above OnSaveSideEffectsAsync_RemovesNonCompliantUsers
to assert that it does not revoke users
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its missing testing when AccountDeprovisioning is enabled and users are revoked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're missing a test for the happy path when account deprovisioning is enabled and users have master passwords (should call RevokeNonCompliantOrganizationUsersAsync)
|
||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>().DidNotReceiveWithAnyArgs() | ||
.RemoveUserAsync(organizationId: default, organizationUserId: default, deletingUserId: default); | ||
} | ||
|
||
[Theory, BitAutoData] | ||
public async Task OnSaveSideEffectsAsync_GivenUpdateTo2faPolicy_WhenAccountProvisioningIsDisabled_ThenRevokeUserCommandShouldNotBeCalled( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OnSaveSideEffectsAsync_RemovesNonCompliantUsers
could verify both the removal of users AND that revoke isn't called and then this test isn't required
{ | ||
if (_featureService.IsEnabled(FeatureFlagKeys.Pm13322AddPolicyDefinitions)) | ||
{ | ||
// Transitional mapping - this will be moved to callers once the feature flag is removed | ||
// TODO make sure to populate with SystemUser if not an actual user |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO no longer needed?
@@ -58,4 +58,6 @@ UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, | |||
/// Returns a list of OrganizationUsers with email domains that match one of the Organization's claimed domains. | |||
/// </summary> | |||
Task<ICollection<OrganizationUser>> GetManyByOrganizationWithClaimedDomainsAsync(Guid organizationId); | |||
|
|||
Task RevokeOrganizationUserAsync(IEnumerable<Guid> userIds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add xmldoc here. Also, I would suggest renaming this to fit in with our usual naming scheme:
Task RevokeOrganizationUserAsync(IEnumerable<Guid> userIds); | |
Task RevokeManyByUserIdAsync(IEnumerable<Guid> userIds); |
.Received(1) | ||
.RevokeOrganizationUserAsync(Arg.Any<IEnumerable<Guid>>()); | ||
|
||
Assert.True(result.Success); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verify event logging for the revoked user
🎟️ Tracking
PM-10319
📔 Objective
This change revokes non-compliant users when 2FA or Single Org policies are enabled. This adds the a
RevokeNonCompliantOrganizationUserCommand
along with a bulk set status stored procedure. It also adds two new email templates to be sent when a user is revoked from an organization.📸 Screenshots
Two FA Policy Revocation Email
Single Org Policy Email
⏰ Reminders before review
🦮 Reviewer guidelines
:+1:
) or similar for great changes:memo:
) or ℹ️ (:information_source:
) for notes or general info:question:
) for questions:thinking:
) or 💭 (:thought_balloon:
) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion:art:
) for suggestions / improvements:x:
) or:warning:
) for more significant problems or concerns needing attention:seedling:
) or ♻️ (:recycle:
) for future improvements or indications of technical debt:pick:
) for minor or nitpick changes