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-15621] Refactor DeleteManagedOrganizationUserAccountCommand #5345

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

JimmyVo16
Copy link
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-15621

📔 Objective

  1. Refactored DeleteUserAsync to call DeleteManyUsersAsync.
  2. Removed the logic for deleting an organization. Since all organizations require at least one owner, EnsureUserIsNotSoleOrganizationOwnerAsync prevents the code from reaching this point.
  3. Separated validation from canceling membership.

📸 Screenshots

I think test coverage is sufficient here, and it's low risk since it's behind a feature, but I did do a quick smoke test by starting up the service.

⏰ 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

@JimmyVo16 JimmyVo16 requested a review from a team as a code owner January 29, 2025 22:31
@JimmyVo16 JimmyVo16 requested a review from BTreston January 29, 2025 22:31
Copy link

codecov bot commented Jan 29, 2025

Codecov Report

Attention: Patch coverage is 94.44444% with 5 lines in your changes missing coverage. Please review.

Project coverage is 44.52%. Comparing base (c82908f) to head (b575458).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...ers/DeleteManagedOrganizationUserAccountCommand.cs 94.44% 2 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5345      +/-   ##
==========================================
+ Coverage   44.48%   44.52%   +0.04%     
==========================================
  Files        1533     1533              
  Lines       71075    71082       +7     
  Branches     6377     6375       -2     
==========================================
+ Hits        31618    31652      +34     
+ Misses      38090    38067      -23     
+ Partials     1367     1363       -4     

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

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

github-actions bot commented Jan 29, 2025

Logo
Checkmarx One – Scan Summary & Details3f6f317f-f89d-4fd0-b270-ed93c93e46fd

New Issues (7)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/Controllers/CollectionsController.cs: 143
detailsMethod Post at line 143 of /src/Api/Controllers/CollectionsController.cs gets a parameter from a user request from model. This parameter value flow...
Attack Vector
MEDIUM CSRF /src/Api/Public/Controllers/CollectionsController.cs: 87
detailsMethod Put at line 87 of /src/Api/Public/Controllers/CollectionsController.cs gets a parameter from a user request from model. This parameter value...
Attack Vector
MEDIUM CSRF /src/Api/Controllers/CollectionsController.cs: 171
detailsMethod Put at line 171 of /src/Api/Controllers/CollectionsController.cs gets a parameter from a user request from model. This parameter value flows...
Attack Vector
MEDIUM Privacy_Violation /src/Core/Auth/Services/Implementations/AuthRequestService.cs: 230
detailsMethod UpdateAuthRequestAsync at line 230 of /src/Core/Auth/Services/Implementations/AuthRequestService.cs sends user information outside the appli...
Attack Vector
MEDIUM Privacy_Violation /src/Core/Auth/Services/Implementations/AuthRequestService.cs: 221
detailsMethod UpdateAuthRequestAsync at line 221 of /src/Core/Auth/Services/Implementations/AuthRequestService.cs sends user information outside the appli...
Attack Vector
MEDIUM Privacy_Violation /src/Core/Auth/Services/Implementations/AuthRequestService.cs: 173
detailsMethod CreateAuthRequestAsync at line 173 of /src/Core/Auth/Services/Implementations/AuthRequestService.cs sends user information outside the appli...
Attack Vector
MEDIUM Privacy_Violation /src/Core/NotificationHub/NotificationHubPushNotificationService.cs: 306
detailsMethod PushAuthRequestAsync at line 306 of /src/Core/NotificationHub/NotificationHubPushNotificationService.cs sends user information outside the a...
Attack Vector
Fixed Issues (22)

Great job! The following issues were fixed in this Pull Request

Severity Issue Source File / Package
MEDIUM CSRF /src/Api/AdminConsole/Controllers/GroupsController.cs: 164
MEDIUM CSRF /src/Api/AdminConsole/Controllers/GroupsController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164

@eliykat eliykat self-requested a review January 30, 2025 00:00
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.

Nice work @JimmyVo16 , I think this is an improvement. I also have some thoughts about the try/catch blocks here which I shared in our dev channel - I wanted to run it past the team before asking for it here, but let me know what you think.

@JimmyVo16 JimmyVo16 requested a review from eliykat February 4, 2025 20:16
Copy link
Contributor

@BTreston BTreston left a comment

Choose a reason for hiding this comment

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

There are some failing tests in DeleteManagedOrganizationUserAccountCommandTests

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.

Just a few minor items.

I think the userDeletionResults tuple makes sense (that is, we need to match these objects up and then pass them around together), but is very verbose and awkward. Definitely a case for better/stronger typed ValidationResult classes, which we should experiment with in the future (whoever gets there first, really).

private async Task LogDeletedOrganizationUsersAsync(
IEnumerable<OrganizationUser> orgUsers,
IEnumerable<(Guid OrgUserId, string? ErrorMessage)> results)
private async Task EnsureUserIsNotSoleOrganizationOwnerAsync(User user)
Copy link
Member

Choose a reason for hiding this comment

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

This overlaps with PreventOrganizationSoleOwnerDeletion (maybe what you meant to refer to in your comment above?)

  • PreventOrganizationSoleOwnerDeletion prevents you from deleting the sole owner of this organization
  • EnsureUserIsNotSoleOrganizationOwnerAsync prevents you from deleting the sole owner of any organization

I suggest we can just rely on the latter given that it obviously includes the former. Both also require database calls, and we don't want to hit the db unnecessarily.

I also question whether PreventOrganizationSoleOwnerDeletion would ever be tripped, given that you can't delete yourself and only owners can delete other owners. If you're the sole owner, who would be able to delete you?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After much thinking, yeah i think about right. EnsureUserIsNotSoleOrganizationOwnerAsync should handle all cases for use, so we can remove PreventOrganizationSoleOwnerDeletion.

(me thinking out loud) These logics are pretty complex. I wonder if there is a way to make them discoverable than deep in a validation method.

Copy link
Member

Choose a reason for hiding this comment

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

(me thinking out loud) These logics are pretty complex. I wonder if there is a way to make them discoverable than deep in a validation method.

I would say validation is the primary source of complexity in our commands (and in old service code that should be commands). It's definitely a strong argument for making this code as simple and as easy to follow as possible.

In terms of discoverability/location, I believe Jared had previously mooted separate Validator classes which encapsulate the validation logic for a given command. Not sure if that makes it more discoverable, but it would provide a stronger separation than private methods.

@eliykat
Copy link
Member

eliykat commented Feb 6, 2025

Also - tests are failing. At a glance, maybe just because the order of validation has changed?

JimmyVo16 and others added 4 commits February 10, 2025 08:55
…eleteManagedOrganizationUserAccountCommand.cs

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
…eleteManagedOrganizationUserAccountCommand.cs

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
eliykat
eliykat previously approved these changes Feb 12, 2025
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.

Great work @JimmyVo16 !

EDIT: putting a needs-qa label on it because it needs QA before merge. However it would be ideal if QA can hit it before we release the larger feature, can you please mention this to them?

@JimmyVo16 JimmyVo16 removed the request for review from BTreston February 13, 2025 15:13
@JimmyVo16 JimmyVo16 dismissed BTreston’s stale review February 13, 2025 15:15

I addressed Brandon's comment, and I think he's out today, so I don't want this to block QA.

@JimmyVo16
Copy link
Contributor Author

Update: QA found an edge case that I missed.

Changes:

  1. For single user deletion, throw an exception if there is an error like the existing behavior.
  2. Add test coverage for this.
  3. Create InternalDeleteManyUsersAsync that would handle all the deletion logic then have DeleteManyUsersAsync and DeleteUserAsync handle the interfaces their clients expects.

Testing for the edge case

Testing.for.self.delete.mov

Regression testing

Happy.path.for.multiple.user.deletions.mov
happy.path.for.single.user.deletion.mov

@eliykat eliykat self-requested a review February 17, 2025 01:42
Comment on lines 62 to 65
if (exception != null)
{
throw exception;
}
Copy link
Member

Choose a reason for hiding this comment

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

This interface is inconsistent - DeleteUserAsync throws if there's a validation error, whereas DeleteManyUsersAsync returns validation results up to the caller. I think the latter is a better pattern.

I suggest that DeleteUserAsync can still call DeleteUsersAsync (how it was before), but it can just return the single result out to the caller; then the caller can handle it. (Which also makes the controller code consistent.)

This method would therefore return a Task<(Guid OrganizationUserId, string? ErrorMessage)>. Or in this case, the OrganizationUserId is a bit redundant, so maybe just a string? ErrorMessage. If you wanted to take this a step further you could use the CommandResult class instead of nullable strings, but I understand if you don't want to do that at this late stage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the interfaces are inconsistent, but they're the existing interfaces. I am trying to avoid breaking them.

With that said, I do think using CommandResult is the right call here and moving away from using exceptions to handle validation errors, but I notice a problem with just returning the error message(s) because, for the single resource endpoint, the HTTP code will be different depending on the error message, unlike the multiple resources endpoint where the HTTP code is the same regardless of the error message.

I'm going to move this conversation to the AC team Slack channel, so we can iterate quicker.

Comment on lines 110 to 112
catch (Exception exception)
{
userDeletionResults.Add((orgUserId, orgUser, user, ex.Message));
userDeletionResults.Add((orgUserId, orgUser, user, exception));
Copy link
Member

Choose a reason for hiding this comment

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

I understand why you made this change, but given my feedback above I think it's unnecessary. This change also means the try/catch pattern is leaking out to other code (because we need it to give us the Exception) which I'd prefer not to do.

@JimmyVo16 JimmyVo16 changed the title [pm-15621] Refactor DeleteManagedOrganizationUserAccountCommand Draft [pm-15621] Refactor DeleteManagedOrganizationUserAccountCommand Mar 7, 2025
@JimmyVo16 JimmyVo16 changed the title Draft [pm-15621] Refactor DeleteManagedOrganizationUserAccountCommand [pm-15621] Refactor DeleteManagedOrganizationUserAccountCommand Mar 7, 2025
return await _userRepository.GetManyAsync(userIds);
}

private async Task<CommandResult> ValidateAsync(Guid organizationId, OrganizationUser orgUser, User user, Guid? deletingUserId, IDictionary<Guid, bool> managementStatus)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jrmccannon Check out this ValidateAsync

Copy link

sonarqubecloud bot commented Mar 7, 2025

@JimmyVo16 JimmyVo16 marked this pull request as draft March 7, 2025 18:48
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.

I like the use of CommandResult and how the validators are executed. I left some comments in your related PR about the CommandResultValidator but otherwise this is looking good to me once tests are passing.

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

Successfully merging this pull request may close these issues.

3 participants