Skip to content
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

Open
wants to merge 33 commits into
base: main
Choose a base branch
from

Conversation

jrmccannon
Copy link
Contributor

@jrmccannon jrmccannon commented Nov 13, 2024

🎟️ 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
image

Single Org Policy Email
image

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 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

@jrmccannon jrmccannon requested review from a team as code owners November 13, 2024 20:38
@jrmccannon jrmccannon changed the title [PM-10319] [PM-10319] - Revoke Non Complaint Users for 2FA and Single Org Policy Enablement Nov 13, 2024
Copy link
Contributor

github-actions bot commented Nov 13, 2024

Logo
Checkmarx One – Scan Summary & Details12dcc08e-ac28-497d-9358-790cbcf0e21f

New Issues

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationsController.cs: 240 Attack Vector
MEDIUM CSRF /src/Api/Auth/Controllers/TwoFactorController.cs: 438 Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs: 97 Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs: 97 Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs: 97 Attack Vector
LOW Unsafe_Use_Of_Target_blank /src/Core/MailTemplates/Handlebars/AdminConsole/OrganizationUserRevokedForTwoFactorPolicy.html.hbs: 11 Attack Vector
LOW Unsafe_Use_Of_Target_blank /src/Admin/AdminConsole/Views/Providers/Edit.cshtml: 103 Attack Vector
LOW Unsafe_Use_Of_Target_blank /src/Admin/AdminConsole/Views/Providers/Edit.cshtml: 90 Attack Vector

Fixed Issues

Severity Issue Source File / Package
MEDIUM CSRF /src/Admin/AdminConsole/Controllers/OrganizationsController.cs: 372
LOW Unsafe_Use_Of_Target_blank /src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseRemovedFromFamilyUser.html.hbs: 11

Copy link

codecov bot commented Nov 13, 2024

Codecov Report

Attention: Patch coverage is 56.55431% with 116 lines in your changes missing coverage. Please review.

Project coverage is 43.06%. Comparing base (8b1b078) to head (1d77345).
Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
...icies/PolicyValidators/SingleOrgPolicyValidator.cs 21.42% 32 Missing and 1 partial ⚠️
.../Services/Implementations/HandlebarsMailService.cs 4.00% 24 Missing ⚠️
...lidators/TwoFactorAuthenticationPolicyValidator.cs 69.64% 10 Missing and 7 partials ⚠️
...Users/RevokeNonCompliantOrganizationUserCommand.cs 79.66% 7 Missing and 5 partials ⚠️
...Console/Repositories/OrganizationUserRepository.cs 0.00% 7 Missing ⚠️
...Console/Repositories/OrganizationUserRepository.cs 0.00% 7 Missing ⚠️
...nizationDomains/VerifyOrganizationDomainCommand.cs 87.50% 3 Missing and 1 partial ⚠️
...nConsole/Services/Implementations/PolicyService.cs 42.85% 4 Missing ⚠️
src/Core/AdminConsole/Models/Data/SystemUser.cs 57.14% 3 Missing ⚠️
...re/Services/NoopImplementations/NoopMailService.cs 0.00% 2 Missing ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5037      +/-   ##
==========================================
+ Coverage   42.68%   43.06%   +0.37%     
==========================================
  Files        1406     1419      +13     
  Lines       65090    64981     -109     
  Branches     5969     5950      -19     
==========================================
+ Hits        27782    27982     +200     
+ Misses      36063    35752     -311     
- Partials     1245     1247       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


🚨 Try these New Features:

@eliykat eliykat requested a review from r-tome November 15, 2024 07:22
Copy link
Contributor

@r-tome r-tome left a 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

jrmccannon and others added 7 commits November 15, 2024 09:45
…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.
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 102 to 109
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)));
}
Copy link
Member

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.

Copy link
Contributor Author

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.

var result = await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync(
new RevokeOrganizationUsersRequest(organizationId, revocableOrgUsers,
new StandardUser(savingUserId ?? Guid.Empty,
await _currentContext.OrganizationOwner(organizationId))));
Copy link
Member

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.

Copy link
Contributor Author

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));
Copy link
Contributor

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?

Copy link
Contributor Author

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?

@@ -126,4 +127,69 @@ await sutProvider.GetDependency<IMailService>()
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(),
"[email protected]");
}

[Theory, BitAutoData]
public async Task OnSaveSideEffectsAsync_GivenUpdatedSingleOrgPolicy_WhenAccountDeprovisioningIsDisabled_Then(
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Contributor

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(
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Contributor

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:

Suggested change
Task RevokeOrganizationUserAsync(IEnumerable<Guid> userIds);
Task RevokeManyByUserIdAsync(IEnumerable<Guid> userIds);

.Received(1)
.RevokeOrganizationUserAsync(Arg.Any<IEnumerable<Guid>>());

Assert.True(result.Success);
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants