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 38 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 & Detailsa1ced24f-c026-464d-9c9e-c2d1a2e0dc6d

New Issues

Severity Issue Source File / Package Checkmarx Insight
MEDIUM Missing_HSTS_Header /src/Admin/Views/Home/Index.cshtml: 14 Attack Vector
LOW Log_Forging /src/Api/Auth/Controllers/TwoFactorController.cs: 406 Attack Vector
LOW Missing_CSP_Header /src/Core/MailTemplates/Handlebars/FamiliesForEnterprise/FamiliesForEnterpriseRemovedFromFamilyUser.html.hbs: 5 Attack Vector
LOW Unsafe_Use_Of_Target_blank /src/Core/MailTemplates/Handlebars/AdminConsole/OrganizationUserRevokedForTwoFactorPolicy.html.hbs: 11 Attack Vector

Fixed Issues

Severity Issue Source File / Package
MEDIUM CSRF /src/Api/AdminConsole/Public/Controllers/MembersController.cs: 95
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationsController.cs: 122
MEDIUM CSRF /src/Admin/AdminConsole/Controllers/OrganizationsController.cs: 372
LOW Log_Forging /src/Api/AdminConsole/Controllers/ProvidersController.cs: 72
LOW Log_Forging /src/Api/AdminConsole/Controllers/ProvidersController.cs: 72
LOW Log_Forging /src/Api/AdminConsole/Controllers/ProvidersController.cs: 72

Copy link

codecov bot commented Nov 13, 2024

Codecov Report

Attention: Patch coverage is 67.03704% with 89 lines in your changes missing coverage. Please review.

Project coverage is 43.13%. Comparing base (92b94fd) to head (66d5831).

Files with missing lines Patch % Lines
.../Services/Implementations/HandlebarsMailService.cs 4.00% 24 Missing ⚠️
...icies/PolicyValidators/SingleOrgPolicyValidator.cs 68.88% 8 Missing and 6 partials ⚠️
...Users/RevokeNonCompliantOrganizationUserCommand.cs 81.35% 6 Missing and 5 partials ⚠️
...lidators/TwoFactorAuthenticationPolicyValidator.cs 82.14% 4 Missing and 6 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   43.09%   43.13%   +0.04%     
==========================================
  Files        1414     1421       +7     
  Lines       64808    65014     +206     
  Branches     5925     5955      +30     
==========================================
+ Hits        27929    28045     +116     
- Misses      35645    35716      +71     
- Partials     1234     1253      +19     

☔ 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.


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?

Copy link
Member

@eliykat eliykat Nov 21, 2024

Choose a reason for hiding this comment

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

I'm not sure about this method specifically, but generally:

  • the Public API and SCIM - CurrentContext.UserId is null because it's the organization that's authenticated, not the user
  • a system call (jobs) - also not tied to a user

A regular call from a client through the private api will always have UserId populated (to my knowledge) because CurrentContext is always initialized in the middleware and the private api always requires user authentication.

What this means for you here I'm not sure. This method seems specifically designed to be called by a user, so throwing if there is no UserId available seems reasonable. And if the UserId is null for some reason, then _currentContext.OrganizationOwner is probably not going to be accurate either (as this is also user specific). Really it seems like an invalid state.

{
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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This TODO will get addressed with a subsequent story.

Copy link
Member

@eliykat eliykat Nov 21, 2024

Choose a reason for hiding this comment

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

I can do this when I remove the policy definition feature flag shortly. Callers will have to construct their own PolicyUpdate objects, so I'll need to pass in the right ActingUser at that time.

Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

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

Looking good, just a couple of things.

Also see my feedback above re: empty guid vs. throwing.

Comment on lines +92 to +93
performedBy is StandardUser stdUser &&
stdUser.UserId != ou.UserId)
Copy link
Member

Choose a reason for hiding this comment

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

I think these last conditions need to be nested. If the ActingUser is a SystemUser, this will never evaluate to true for anyone.

Comment on lines -58 to +126
var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers);
var removableOrgUsers = orgUsers.Where(ou =>
ou.Status != OrganizationUserStatusType.Invited && ou.Status != OrganizationUserStatusType.Revoked &&
ou.Type != OrganizationUserType.Owner && ou.Type != OrganizationUserType.Admin &&
ou.UserId != savingUserId);

// this can get moved into the private method after AccountDeprovisiong flag check is removed.
var organizationUsersTwoFactorEnabled =
(await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers)).ToList();

var revocableOrgUsers = orgUsers.Where(ou =>
ou.Status != OrganizationUserStatusType.Invited && ou.Status != OrganizationUserStatusType.Revoked &&
ou.Type != OrganizationUserType.Owner && ou.Type != OrganizationUserType.Admin &&
ou.UserId != savingUserId)
.ToList();
Copy link
Member

Choose a reason for hiding this comment

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

There are unreverted changes here. I think it's functionally the same but would be best to properly revert. (e.g. it still refers to revocableOrgUsers.)

Comment on lines +82 to +83
performedBy is StandardUser stdUser &&
stdUser.UserId != ou.UserId)
Copy link
Member

Choose a reason for hiding this comment

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

Same here

var organizationUsersTwoFactorEnabled =
await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(currentActiveRevocableOrganizationUsers);

if (DoMembersNotHaveAPasswordWithTwoFactorAuthenticationEnabled(currentActiveRevocableOrganizationUsers, organizationUsersTwoFactorEnabled))
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure this method name is accurate; shouldn't it be "WithoutTwoFactorAuthenticationEnabled"? Or maybe the double negative is throwing me. How about if (NonCompliantMembersWillLoseAccess)?

Comment on lines +133 to +152
if (isAccountDeprovisioningEnabled)
{
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
.Received(1)
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>());
await sutProvider.GetDependency<IMailService>()
.Received(1)
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(),
"[email protected]");
}
else
{
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
.Received(1)
.RemoveUserAsync(policyUpdate.OrganizationId, nonCompliantUser.Id, savingUserId);
await sutProvider.GetDependency<IMailService>()
.Received(1)
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(),
"[email protected]");
}
Copy link
Member

Choose a reason for hiding this comment

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

Not a blocker, but I generally avoid conditional assertions in tests; this suggests to me that you should just have 2 tests. That's easier to clean up as well, because you can just delete the whole test.

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