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-16811] - SCIM Invite Users Optimizations #5398

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

Conversation

jrmccannon
Copy link
Contributor

@jrmccannon jrmccannon commented Feb 12, 2025

🎟️ Tracking

PM-16811

📔 Objective

The goal of this PR is to improve the Invite Organization User flow from the SCIM POST endpoint. It will be later used to optimize the InviteOrganizationUser flow.

This was an attempt to surface all validation of inviting a user to the front of the call and to reduce duplicate database calls and validations.

This adds two a few new wrapper classes to allow for better flow control for when a request fails. By adding ValidationResult and a typed CommandResult, we can "fail" a request for different reasons and allow the caller to decide what to do with the failure.

Please refer to the diagram below of the new call stack.

📸 Diagram

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

Copy link

codecov bot commented Feb 12, 2025

Codecov Report

Attention: Patch coverage is 78.71537% with 169 lines in your changes missing coverage. Please review.

Project coverage is 47.97%. Comparing base (abfdf6f) to head (829e49c).
Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
...sers/InviteUsers/InviteOrganizationUsersCommand.cs 76.82% 32 Missing and 3 partials ⚠️
...ers/Validation/InviteOrganizationUserValidation.cs 50.00% 16 Missing and 7 partials ⚠️
...itwarden_license/src/Scim/Users/PostUserCommand.cs 72.91% 9 Missing and 4 partials ⚠️
...ider/InvitingUserOrganizationProviderValidation.cs 0.00% 12 Missing ⚠️
...e/Services/Implementations/StripePaymentService.cs 0.00% 9 Missing ⚠️
...wordManager/PasswordManagerInviteUserValidation.cs 63.63% 5 Missing and 3 partials ⚠️
...rs/InviteUsers/Validation/SecretsManager/Errors.cs 27.27% 8 Missing ⚠️
...cretsManager/SecretsManagerInviteUserValidation.cs 76.47% 5 Missing and 3 partials ⚠️
...ers/InviteUsers/Validation/Provider/ProviderDto.cs 0.00% 7 Missing ⚠️
...AdminConsole/Shared/Validation/ValidationResult.cs 68.42% 6 Missing ⚠️
... and 20 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5398      +/-   ##
==========================================
+ Coverage   44.50%   47.97%   +3.47%     
==========================================
  Files        1542     1571      +29     
  Lines       70658    71399     +741     
  Branches     6322     6392      +70     
==========================================
+ Hits        31448    34257    +2809     
+ Misses      37864    35708    -2156     
- Partials     1346     1434      +88     

☔ 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 Feb 12, 2025

Logo
Checkmarx One – Scan Summary & Detailse818f4da-93e1-4757-bf5b-3af57afea366

New Issues (4)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/AdminConsole/Controllers/GroupsController.cs: 164
detailsMethod Put at line 164 of /src/Api/AdminConsole/Controllers/GroupsController.cs gets a parameter from a user request from model. This parameter val...
Attack Vector
MEDIUM CSRF /bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs: 94
detailsMethod Put at line 94 of /bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs gets a parameter from a user request from model. This param...
Attack Vector
MEDIUM CSRF /src/Api/AdminConsole/Public/Controllers/GroupsController.cs: 133
detailsMethod Put at line 133 of /src/Api/AdminConsole/Public/Controllers/GroupsController.cs gets a parameter from a user request from model. This parame...
Attack Vector
MEDIUM CSRF /bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs: 104
detailsMethod Patch at line 104 of /bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs gets a parameter from a user request from model. This pa...
Attack Vector
Fixed Issues (2)

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

Severity Issue Source File / Package
LOW Use_Of_Hardcoded_Password /src/Core/Constants.cs: 185
LOW Use_Of_Hardcoded_Password /src/Core/Constants.cs: 184

Copy link
Contributor

LaunchDarkly flag references

🔍 1 flag added or modified

Name Key Aliases found Info
Optimize Invite User flow to fail fast pm-16811-optimize-invite-user-flow-to-fail-fast

@eliykat eliykat self-requested a review February 13, 2025 00:37
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.

Here are some initial thoughts, broadly limited to the controller, commands, and various models. TODO: review the validation, tests, and generic Result/Validation classes. For that reason some of my questions/thoughts may be answered by further review, but I wanted to get something checked in.

var organization = await _organizationRepository.GetByIdAsync(organizationId);
var hasStandaloneSecretsManager = await _paymentService.HasSecretsManagerStandalone(organization);
var organization = await organizationRepository.GetByIdAsync(organizationId);
var hasStandaloneSecretsManager = await paymentService.HasSecretsManagerStandalone(organization);
Copy link
Member

Choose a reason for hiding this comment

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

paymentService hides a call to the Stripe API, which we want to defer for as long as possible. I think you can move this into the command. Today it's an argument that's passed in, but that just means that it's repeated in every single calling location, which is unnecessary code duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If a company has secrets manager, every invite will be for both PM and SM?

Copy link
Member

Choose a reason for hiding this comment

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

Not quite - it depends how they've signed up. I've sent you a DM with the background here because it's lengthy and confusing.

Comment on lines 287 to 289
_factory.SubstituteService((IFeatureService featureService)
=> featureService.IsEnabled(FeatureFlagKeys.ScimInviteUserOptimization)
.Returns(isScimInviteUserOptimizationEnabled));
Copy link
Member

Choose a reason for hiding this comment

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

Nice idea, but this is too late to affect the creation of the services. Try using the debugger - your new code is not being executed in either test run.

I found that I had to put it in the constructor of the class, e.g. https://github.com/bitwarden/server/blob/main/bitwarden_license/test/Scim.IntegrationTest/Controllers/v2/GroupsControllerPatchTestsvNext.cs#L24-L29. Unfortunately that does require duplication at the class level. There may be a better way to do it (ask Justin if you want to dig into this further).

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 found I could do it at the individual test by creating a new factory and reinitializing the db.

Comment on lines 21 to 30
new(organization.Id,
organization.UseCustomPermissions,
organization.Seats,
organization.MaxAutoscaleSeats,
organization.SmSeats,
organization.MaxAutoscaleSmSeats,
StaticStore.GetPlan(organization.PlanType),
organization.GatewayCustomerId,
organization.GatewaySubscriptionId,
organization.UseSecretsManager);
Copy link
Member

Choose a reason for hiding this comment

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

It's more boilerplate, but I strongly prefer named arguments here when we have so many weakly typed parameters (e.g. ints). Sorry about your fancy record syntax ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its fine. It can still be a record though :D

Copy link
Member

Choose a reason for hiding this comment

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

This seems like a specific selection of Organization properties, scoped to what's specifically required by this command. Maybe the name should reflect this - e.g. InviteOrganization?

Copy link
Member

Choose a reason for hiding this comment

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

Great work moving this into a separate class. It's definitely its own weird bespoke logic.

@eliykat eliykat self-requested a review March 5, 2025 04:17
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.

Some first thoughts here, I'll continue reviewing this later

Comment on lines 20 to 30
public static OrganizationDto FromOrganization(Organization organization) =>
new(organization.Id,
organization.UseCustomPermissions,
organization.Seats,
organization.MaxAutoscaleSeats,
organization.SmSeats,
organization.MaxAutoscaleSmSeats,
StaticStore.GetPlan(organization.PlanType),
organization.GatewayCustomerId,
organization.GatewaySubscriptionId,
organization.UseSecretsManager);
Copy link
Contributor

Choose a reason for hiding this comment

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

Constructors are the standard C# way for creating objects so I think using a new constructor is more intuitive

Suggested change
public static OrganizationDto FromOrganization(Organization organization) =>
new(organization.Id,
organization.UseCustomPermissions,
organization.Seats,
organization.MaxAutoscaleSeats,
organization.SmSeats,
organization.MaxAutoscaleSmSeats,
StaticStore.GetPlan(organization.PlanType),
organization.GatewayCustomerId,
organization.GatewaySubscriptionId,
organization.UseSecretsManager);
public OrganizationDto(Organization organization)
: this(
OrganizationId: organization.Id,
UseCustomPermissions: organization.UseCustomPermissions,
Seats: organization.Seats,
MaxAutoScaleSeats: organization.MaxAutoscaleSeats,
SmSeats: organization.SmSeats,
SmMaxAutoScaleSeats: organization.MaxAutoscaleSmSeats,
Plan: StaticStore.GetPlan(organization.PlanType),
GatewayCustomerId: organization.GatewayCustomerId,
GatewaySubscriptionId: organization.GatewaySubscriptionId,
UseSecretsManager: organization.UseSecretsManager)
{
}

Comment on lines 23 to 27
public static InviteOrganizationUsersRequest Create(InviteScimOrganizationUserRequest request) =>
new([OrganizationUserInvite.Create(request.Invite, request.ExternalId)],
request.Organization,
Guid.Empty,
request.PerformedAt);
Copy link
Contributor

Choose a reason for hiding this comment

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

I may not be understanding why these static methods are better than constructors

Suggested change
public static InviteOrganizationUsersRequest Create(InviteScimOrganizationUserRequest request) =>
new([OrganizationUserInvite.Create(request.Invite, request.ExternalId)],
request.Organization,
Guid.Empty,
request.PerformedAt);
public InviteOrganizationUsersRequest(InviteScimOrganizationUserRequest request)
: this(
Invites: [OrganizationUserInvite.Create(request.Invite, request.ExternalId)],
Organization: request.Organization,
PerformedBy: Guid.Empty,
PerformedAt: request.PerformedAt)
{
}

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 prefer the way the Create static method reads in code. Especially when working with Func. It also allows you to put some validation logic into the method, which I prefer over putting in the constructor.

I can switch over to constructors since that seems to be others' preference.

Copy link
Contributor

Choose a reason for hiding this comment

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

That is fair, I can see why you don't want logic inside the constructor. But it's not very intuitive that to create an object you have to use the Create method over the constructor.

@jrmccannon jrmccannon marked this pull request as ready for review March 17, 2025 20:19
@jrmccannon jrmccannon requested review from a team as code owners March 17, 2025 20:19
@jrmccannon jrmccannon requested a review from r-tome March 18, 2025 20:13
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.

Overall it looks good to me. I mainly think it needs documentation because sometimes I felt lost at what I was looking at (big PR doesn't help). I'll review this again tomorrow


namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;

public interface ISendOrganizationInvitesCommand
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


namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;

public interface IInviteOrganizationUsersCommand
Copy link
Contributor

Choose a reason for hiding this comment

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

Xmldoc here please

return new Success<IEnumerable<OrganizationUser>>(organizationUserCollection.Select(x => x.OrganizationUser));
}

private async Task RevertPasswordManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> valid, Organization organization)
Copy link
Contributor

Choose a reason for hiding this comment

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

Keeping consistency on the name helps readability. Other methods also

Suggested change
private async Task RevertPasswordManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> valid, Organization organization)
private async Task RevertPasswordManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> validatedRequest, Organization organization)


namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;

public class CreateOrganizationUser
Copy link
Contributor

Choose a reason for hiding this comment

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

Add some xmldoc here or maybe rename it to something that would indicate what this is used for.

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

Successfully merging this pull request may close these issues.

3 participants