Skip to content

Conversation

@eliykat
Copy link
Member

@eliykat eliykat commented Aug 12, 2025

🎟️ Tracking

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

📔 Objective

Move the recover account logic out of UserService and into its own command. See ticket for more information.

This is generally a lift & shift, except for the new CheckProviderPermissionsAsync private method, and tests (which are all new).

📸 Screenshots

⏰ 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

@eliykat eliykat marked this pull request as ready for review August 12, 2025 01:30
@eliykat eliykat requested a review from a team as a code owner August 12, 2025 01:30
@eliykat eliykat requested a review from JimmyVo16 August 12, 2025 01:30
@codecov
Copy link

codecov bot commented Aug 12, 2025

Codecov Report

❌ Patch coverage is 96.87500% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.98%. Comparing base (cfe818e) to head (1722a57).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...ures/AccountRecovery/AdminRecoverAccountCommand.cs 93.47% 2 Missing and 1 partial ⚠️
...Console/Controllers/OrganizationUsersController.cs 96.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6184      +/-   ##
==========================================
+ Coverage   51.86%   51.98%   +0.11%     
==========================================
  Files        1902     1907       +5     
  Lines       84058    84330     +272     
  Branches     7501     7530      +29     
==========================================
+ Hits        43598    43840     +242     
- Misses      38766    38784      +18     
- Partials     1694     1706      +12     

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

@github-actions
Copy link
Contributor

github-actions bot commented Aug 12, 2025

Logo
Checkmarx One – Scan Summary & Details09f138cb-b194-4cbe-ad2e-2093d705b5e3

New Issues (1)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationUsersController.cs: 482
detailsMethod at line 482 of /src/Api/AdminConsole/Controllers/OrganizationUsersController.cs gets a parameter from a user request from model. This par...
ID: 8uwD6Z%2BpyEf7Uyx1YNARJuuFSFw%3D
Attack Vector
Fixed Issues (2)

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

Severity Issue Source File / Package
MEDIUM CSRF /src/Api/Dirt/Controllers/OrganizationReportsController.cs: 173
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationUsersController.cs: 477

JimmyVo16
JimmyVo16 previously approved these changes Aug 13, 2025
@eliykat
Copy link
Member Author

eliykat commented Aug 15, 2025

Reverting to draft while I implement some changes requested by Product. Specifically, we want to alert the user before they submit the dialog, so I intend to add a prevalidate endpoint to check permissions at the time the dialog opens. This avoids additional load on the Members endpoint when the page first loads.

@eliykat eliykat marked this pull request as draft August 15, 2025 04:43
@eliykat eliykat force-pushed the ac/pm-24192/server-prevent-organizations-from-recovering-a-provider-account branch from d311b39 to ab76446 Compare September 5, 2025 03:42
@eliykat eliykat changed the base branch from main to ac/pm-24192/organization-context September 5, 2025 03:43
@claude
Copy link

claude bot commented Oct 28, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

JimmyVo16
JimmyVo16 previously approved these changes Oct 29, 2025
Copy link
Contributor

@JimmyVo16 JimmyVo16 left a comment

Choose a reason for hiding this comment

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

Some questions, but no blockers.


public async Task InitializeAsync()
{
_ownerEmail = $"org-user-reset-password-integration-test-{Guid.NewGuid()}@bitwarden.com";
Copy link
Contributor

Choose a reason for hiding this comment

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

Don’t we normally use example.com for test mock-ups?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes we do and yes we should!

var targetUserProviderUsers =
await providerUserRepository.GetManyByUserAsync(targetOrganizationUser.UserId.Value);

if (targetUserProviderUsers.Any(providerUser => !currentContext.ProviderUser(providerUser.ProviderId)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to confirm my understanding: if this user is a provider in any reseller, then we cannot reset their password?

Copy link
Member Author

Choose a reason for hiding this comment

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

The business rule is: to recover a provider user's account, you must also be a member for the same provider(s).

In effect, this code iterates through the target user's ProviderUser associations and calls currentContext to make sure the current user is also a member for the same providers.

If the current user is not a member for the same providers, that indicates a privilege escalation so we block it.

Copy link
Member Author

@eliykat eliykat Oct 30, 2025

Choose a reason for hiding this comment

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

I've added a comment and some xmldoc on ICurrentContext to clarify this a little.

throw new NotFoundException();
}

var authorizationResult = await _authorizationService.AuthorizeAsync(User, targetOrganizationUser, new RecoverAccountAuthorizationRequirement());
Copy link
Contributor

Choose a reason for hiding this comment

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

Just thinking out loud: this looks like it could be in a middleware that we can attach to the controller method. That way, it's cleaner and we can add it to multiple places. In some languages, you can even hydrate the request object in the middleware so the data can be passed along to other middlewares.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is definitely doable, but at this stage we have no cause to reuse this elsewhere, and the logic is already encapsulated in the handler. I think it's clearer to make the explicit call. As you've found with your logging case, moving logic into the middleware isn't always very clear to the reader.

That said, Billing Team do have a neat attribute that fetches and injects the organization for the current request: https://github.com/bitwarden/server/blob/main/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs#L36-L44. We could do something similar to get the orgUser, make sure they exist, and make sure they match the current organization - given that that is a very common but critical bit of boilerplate. I've already fussed this PR a lot though so I'd prefer to leave it for future experimentation.

- Renamed method from PutResetPasswordvNext to PutResetPasswordNew following C# naming conventions                                                                                                                                 │
- Changed visibility from public to private since authorization is handled by the calling method PutResetPassword                                                                                                                  │
- Removed [FromBody] attribute as it's not needed for private methods
- Return TypedResults.NotFound() instead of throwing NotFoundException
- Return TypedResults.BadRequest(ModelState) instead of throwing BadRequestException
- Applies to both PutResetPassword and PutResetPasswordNew methods
- Added #nullable enable before the method
- Added #nullable restore after the method
- Keeps file-level #nullable disable intact for legacy code
- Tests for legacy path when feature flag is disabled
- Tests for new path when feature flag is enabled
- Tests for authorization failures returning BadRequest
- Tests for organization user not found scenarios
- Tests for successful and failed account recovery
- Keep attribute for future when this becomes a public endpoint
- Changed #nullable restore to #nullable disable
- Added note about partial migration for future developers
@eliykat
Copy link
Member Author

eliykat commented Oct 30, 2025

I will take a final look at Claude's feedback tomorrow, but all major issues have been addressed.

This also allows more detailed authorization failure messages
because we are guaranteed an order of execution within the same handler.
@eliykat
Copy link
Member Author

eliykat commented Oct 31, 2025

@JimmyVo16 This is ready for review. The main change I've made is to combine the 2 authorization handlers - the logic is very similar, but now it runs in a single handler in sequence, rather than both handlers being executed separately. I ultimately decided this was clearer than 2 handlers, and allows for more descriptive error messages for each failure case.

Claude's latest review failed because we hit our usage limits. You can see the last review in the comment history, but everything there seems unnecessary or wrong - let me know if you disagree.

@eliykat eliykat requested a review from JimmyVo16 October 31, 2025 05:15
@eliykat eliykat merged commit e114581 into main Oct 31, 2025
41 of 43 checks passed
@eliykat eliykat deleted the ac/pm-24192/server-prevent-organizations-from-recovering-a-provider-account branch October 31, 2025 21:55
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.

3 participants