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

[AC-2489] Resolve SM Standalone issues with SCIM & Directory Connector #4011

Merged
merged 4 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 12 additions & 1 deletion bitwarden_license/src/Scim/Users/PostUserCommand.cs
Expand Up @@ -14,17 +14,23 @@ namespace Bit.Scim.Users;

public class PostUserCommand : IPostUserCommand
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
private readonly IPaymentService _paymentService;
private readonly IScimContext _scimContext;

public PostUserCommand(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService,
IPaymentService paymentService,
IScimContext scimContext)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
_paymentService = paymentService;
_scimContext = scimContext;
}

Expand Down Expand Up @@ -80,8 +86,13 @@ public async Task<OrganizationUserUserDetails> PostUserAsync(Guid organizationId
throw new ConflictException();
}

var organization = await _organizationRepository.GetByIdAsync(organizationId);

var hasStandaloneSecretsManager = await _paymentService.HasSecretsManagerStandalone(organization);

var invitedOrgUser = await _organizationService.InviteUserAsync(organizationId, EventSystemUser.SCIM, email,
OrganizationUserType.User, false, externalId, new List<CollectionAccessSelection>(), new List<Guid>());
OrganizationUserType.User, false, externalId, new List<CollectionAccessSelection>(), new List<Guid>(), hasStandaloneSecretsManager);

var orgUser = await _organizationUserRepository.GetDetailsByIdAsync(invitedOrgUser.Id);

return orgUser;
Expand Down
13 changes: 9 additions & 4 deletions bitwarden_license/test/Scim.Test/Users/PostUserCommandTests.cs
@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
Expand All @@ -19,7 +20,7 @@ public class PostUserCommandTests
{
[Theory]
[BitAutoData]
public async Task PostUser_Success(SutProvider<PostUserCommand> sutProvider, string externalId, Guid organizationId, List<BaseScimUserModel.EmailModel> emails, ICollection<OrganizationUserUserDetails> organizationUsers, Core.Entities.OrganizationUser newUser)
public async Task PostUser_Success(SutProvider<PostUserCommand> sutProvider, string externalId, Guid organizationId, List<BaseScimUserModel.EmailModel> emails, ICollection<OrganizationUserUserDetails> organizationUsers, Core.Entities.OrganizationUser newUser, Organization organization)
{
var scimUserRequestModel = new ScimUserRequestModel
{
Expand All @@ -33,16 +34,20 @@ public async Task PostUser_Success(SutProvider<PostUserCommand> sutProvider, str
.GetManyDetailsByOrganizationAsync(organizationId)
.Returns(organizationUsers);

sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);

sutProvider.GetDependency<IPaymentService>().HasSecretsManagerStandalone(organization).Returns(true);

sutProvider.GetDependency<IOrganizationService>()
.InviteUserAsync(organizationId, EventSystemUser.SCIM, scimUserRequestModel.PrimaryEmail.ToLowerInvariant(),
OrganizationUserType.User, false, externalId, Arg.Any<List<CollectionAccessSelection>>(),
Arg.Any<List<Guid>>())
Arg.Any<List<Guid>>(), true)
.Returns(newUser);

var user = await sutProvider.Sut.PostUserAsync(organizationId, scimUserRequestModel);

await sutProvider.GetDependency<IOrganizationService>().Received(1).InviteUserAsync(organizationId, EventSystemUser.SCIM, scimUserRequestModel.PrimaryEmail.ToLowerInvariant(),
OrganizationUserType.User, false, scimUserRequestModel.ExternalId, Arg.Any<List<CollectionAccessSelection>>(), Arg.Any<List<Guid>>());
OrganizationUserType.User, false, scimUserRequestModel.ExternalId, Arg.Any<List<CollectionAccessSelection>>(), Arg.Any<List<Guid>>(), true);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetDetailsByIdAsync(newUser.Id);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Core/AdminConsole/Services/IOrganizationService.cs
Expand Up @@ -49,7 +49,7 @@ public interface IOrganizationService
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
OrganizationUserType type, bool accessAll, string externalId, ICollection<CollectionAccessSelection> collections, IEnumerable<Guid> groups);
Task<OrganizationUser> InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups);
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups, bool accessSecretsManager);
Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId);
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
Expand Down
Expand Up @@ -1673,22 +1673,23 @@ public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Gu

public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups)
IEnumerable<Guid> groups, bool accessSecretsManager)
{
// Collection associations validation not required as they are always an empty list - created via system user (scim)
return await SaveUserSendInviteAsync(organizationId, invitingUserId: null, systemUser, email, type, accessAll, externalId, collections, groups);
return await SaveUserSendInviteAsync(organizationId, invitingUserId: null, systemUser, email, type, accessAll, externalId, collections, groups, accessSecretsManager);
}

private async Task<OrganizationUser> SaveUserSendInviteAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, string email,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups)
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups, bool accessSecretsManager = false)
{
var invite = new OrganizationUserInvite()
{
Emails = new List<string> { email },
Type = type,
AccessAll = accessAll,
Collections = collections,
Groups = groups
Groups = groups,
AccessSecretsManager = accessSecretsManager
};
var results = systemUser.HasValue ? await InviteUsersAsync(organizationId, systemUser.Value,
new (OrganizationUserInvite, string)[] { (invite, externalId) }) : await InviteUsersAsync(organizationId, invitingUserId,
Expand Down Expand Up @@ -1787,6 +1788,8 @@ public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Gu
enoughSeatsAvailable = seatsAvailable >= usersToAdd.Count;
}

var hasStandaloneSecretsManager = await _paymentService.HasSecretsManagerStandalone(organization);

var userInvites = new List<(OrganizationUserInvite, string)>();
foreach (var user in newUsers)
{
Expand All @@ -1803,6 +1806,7 @@ public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Gu
Type = OrganizationUserType.User,
AccessAll = false,
Collections = new List<CollectionAccessSelection>(),
AccessSecretsManager = hasStandaloneSecretsManager
};
userInvites.Add((invite, user.ExternalId));
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/Services/IPaymentService.cs
Expand Up @@ -57,4 +57,5 @@ public interface IPaymentService
Task<string> AddSecretsManagerToSubscription(Organization org, Plan plan, int additionalSmSeats,
int additionalServiceAccount, DateTime? prorationDate = null);
Task<bool> RisksSubscriptionFailure(Organization organization);
Task<bool> HasSecretsManagerStandalone(Organization organization);
}
12 changes: 12 additions & 0 deletions src/Core/Services/Implementations/StripePaymentService.cs
Expand Up @@ -1800,6 +1800,18 @@
return paymentSource == null;
}

public async Task<bool> HasSecretsManagerStandalone(Organization organization)
{
if (string.IsNullOrEmpty(organization.GatewayCustomerId))
{
return false;
}

var customer = await _stripeAdapter.CustomerGetAsync(organization.GatewayCustomerId);

Check warning on line 1810 in src/Core/Services/Implementations/StripePaymentService.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Services/Implementations/StripePaymentService.cs#L1810

Added line #L1810 was not covered by tests

return customer?.Discount?.Coupon?.Id == SecretsManagerStandaloneDiscountId;
}

private PaymentMethod GetLatestCardPaymentMethod(string customerId)
{
var cardPaymentMethods = _stripeAdapter.PaymentMethodListAutoPaging(
Expand Down