Skip to content

[PM-19145] refactor organization service.import async #5800

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

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

Conversation

BTreston
Copy link
Contributor

@BTreston BTreston commented May 9, 2025

🎟️ Tracking

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

📔 Objective

Refactors import method as a command utilizing InviteOrganizationUsersCommand

📸 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

Copy link
Contributor

github-actions bot commented May 9, 2025

Logo
Checkmarx One – Scan Summary & Details7f591826-0899-4b79-8fc4-aae2e10946aa

New Issues (1)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/AdminConsole/Public/Controllers/OrganizationController.cs: 41
detailsMethod Import at line 41 of /src/Api/AdminConsole/Public/Controllers/OrganizationController.cs gets a parameter from a user request from Import. ...
ID: 1wRx0L6L54GhghCdj9IMHZZF8Kk%3D
Attack Vector

@BTreston BTreston changed the title Ac/pm 19145 refactor organization service.import async [PM-19145] refactor organization service.import async May 9, 2025
Copy link

codecov bot commented May 12, 2025

Codecov Report

Attention: Patch coverage is 56.69291% with 110 lines in your changes missing coverage. Please review.

Project coverage is 47.78%. Comparing base (8165651) to head (7994f61).
Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...OrganizationUsers/ImportOrganizationUserCommand.cs 56.38% 71 Missing and 11 partials ⚠️
...sers/InviteUsers/InviteOrganizationUsersCommand.cs 0.00% 22 Missing ⚠️
...nsole/Public/Controllers/OrganizationController.cs 0.00% 5 Missing ⚠️
.../Data/Organizations/OrganizationGroupImportData.cs 93.75% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5800      +/-   ##
==========================================
+ Coverage   47.52%   47.78%   +0.26%     
==========================================
  Files        1666     1668       +2     
  Lines       74910    75103     +193     
  Branches     6736     6758      +22     
==========================================
+ Hits        35599    35890     +291     
+ Misses      37853    37746     -107     
- Partials     1458     1467       +9     

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

/// Contains the details for inviting the imported organization users.
/// </param>
/// <returns>Response from InviteOrganiationUsersAsync<see cref="InviteOrganizationUsersResponse"/></returns>
Task<CommandResult<InviteOrganizationUsersResponse>> InviteImportedOrganizationUsersAsync(InviteOrganizationUsersRequest request, Guid organizationId);
Copy link
Contributor

Choose a reason for hiding this comment

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

PUt org id into the request object

@BTreston BTreston marked this pull request as ready for review May 15, 2025 21:12
@BTreston BTreston requested a review from a team as a code owner May 15, 2025 21:12
@BTreston BTreston requested a review from eliykat May 15, 2025 21:12
@BTreston BTreston requested a review from jrmccannon May 15, 2025 21:12
{
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
var inviteOrganization = new InviteOrganization(organization, plan);
var request = new InviteOrganizationUsersRequest(invites.ToArray(), inviteOrganization, organization.Id, DateTimeOffset.UtcNow);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should this 3rd param (PerformedBy) be organization.Id or Guid.Empty?

Copy link
Contributor

Choose a reason for hiding this comment

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

Empty

Copy link
Contributor

@jrmccannon jrmccannon 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 first pass on this. I'll continue digging in tomorrow.

OrganizationUserImportData importUserData)
{

var hasStandaloneSecretsManager = await _paymentService.HasSecretsManagerStandalone(organization);
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need to do this. It is handled inside of InviteOrgUsers

if (await paymentService.HasSecretsManagerStandalone(request.InviteOrganization))

NewUsersSet = new HashSet<string>(newUsers?.Select(u => u.ExternalId) ?? new List<string>()),
ExistingUsers = existingUsers,
ExistingExternalUsers = GetExistingExternalUsers(existingUsers),
ExistingExternalUsersIdDict = GetExistingExternalUsers(existingUsers).ToDictionary(u => u.ExternalId, u => u.Id)
Copy link
Contributor

Choose a reason for hiding this comment

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

You could maybe move this to a constructor and have it pass the newUsers and existingUsers as parameters. That way you don't have to call GetExistingExternalUsers twice.

ExistingUsers = existingUsers,
ExistingExternalUsers = GetExistingExternalUsers(existingUsers),
ExistingExternalUsersIdDict = ExistingExternalUsers.ToDictionary(u => u.ExternalId, u => u.Id)

events.AddRange(removeUsersSet.Select(u => (
u,
EventType.OrganizationUser_Removed,
(DateTime?)DateTime.UtcNow
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of DateTime.UtcNow you can use the TimeProvider. It allows you to override it in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are some spots where we have to continue using DateTime.UtcNow until other parts of the system are refactored (calling out the Groups object specifically). This is probably best left for a followup PR.


foreach (var user in newAndExistingUsersIntersection)
{
var organizationUser = organizationUsers[existingUsersEmailsDict[user].Id];
Copy link
Contributor

Choose a reason for hiding this comment

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

If the user doesn't exist, I think this will throw an exception. You want to use TryGetValue() instead

);
}

private async Task UpsertExistingUsers(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.

organization parameter is unused

await OverwriteExisting(events, importUserData);
}

await UpsertExistingUsers(organization, newUsers, importUserData);
Copy link
Contributor

Choose a reason for hiding this comment

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

So the point of this step is to find all existing users that match the email provided and create new org users for them if they don't exist for this organization?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Essentially we are just updating the existing org users (if any) to have the updated externalId that the corresponding user in the importData has.

@BTreston BTreston requested a review from jrmccannon June 4, 2025 14:45
Copy link

sonarqubecloud bot commented Jun 4, 2025

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.

2 participants