mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
Defect/SG-992 ProviderOrgs Missing Plan Type & EC-591/SG-996 - Provider Org Autoscaling Email Invites Working (#2596)
* SG-992 - Provider receives free org prompt when trying to auto scale org seats because plan type was missing and defaulting to free. PlanType has now been added to provider orgs returned as part of the profile sync. * SG-992 - Updated Stored proc name to match convention * EC-591 / SG-996 - (1) Update ProviderUserRepo.GetManyDetailsByProviderAsync to accept optional ProviderUserStatusType (2) Update OrganizationService.cs autoscaling user logic to check if an org is a provider org and send owner emails to the confirmed provider users instead of the managed org owners. Prevents scenario where newly created, managed orgs would not have an owner yet, and ownerEmails would be null and the email service would explode. * EC-591 / SG-996 - Remove comments * EC-591 / SG-996 - ES lint fix. * SG-996 - SQL files must have SQL extensions. * SG-996 / EC-591 - Update alter sql to be actually backwards compatible * SG-996 - Make Status actually optional and backwards compatible for ProviderUserUserDetails_ReadByProvider.sql * SG-992 - Update migrations to meet standards - (1) use CREATE OR ALTER and (2) Update view metadata after change if necessary * EC-591 / SG-996 - Update Stored Proc migration to use proper standards: (1) Remove unnecessary code and (2) Use CREATE OR ALTER instead of just ALTER * SG-992 / EC-591 / SG-996 - Refactor separate migrations into single migrations file per PR feedback * SG-992/SG-996 - Add SyncControllerTests.cs with basic test suite + specific test suite to ensure provider orgs have plan type mapped to output product type properly. * Fix lint issues by removing unnecessary using statements * SG-992 - Refresh of view metadata has to target the stored procs that reference the view -- not the view itself.
This commit is contained in:
parent
6dfbd06e8f
commit
b412a01d2a
@ -1,5 +1,6 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Api.Models.Response;
|
||||
|
||||
@ -39,5 +40,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
|
||||
UserId = organization.UserId?.ToString();
|
||||
ProviderId = organization.ProviderId?.ToString();
|
||||
ProviderName = organization.ProviderName;
|
||||
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product;
|
||||
}
|
||||
}
|
||||
|
@ -34,4 +34,5 @@ public class ProviderUserOrganizationDetails
|
||||
public Guid? ProviderId { get; set; }
|
||||
public Guid? ProviderUserId { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
public Enums.PlanType PlanType { get; set; }
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ public interface IProviderUserRepository : IRepository<ProviderUser, Guid>
|
||||
Task<ICollection<ProviderUser>> GetManyByUserAsync(Guid userId);
|
||||
Task<ProviderUser> GetByProviderUserAsync(Guid providerId, Guid userId);
|
||||
Task<ICollection<ProviderUser>> GetManyByProviderAsync(Guid providerId, ProviderUserType? type = null);
|
||||
Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId);
|
||||
Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId, ProviderUserStatusType? status = null);
|
||||
Task<ICollection<ProviderUserProviderDetails>> GetManyDetailsByUserAsync(Guid userId,
|
||||
ProviderUserStatusType? status = null);
|
||||
Task<IEnumerable<ProviderUserOrganizationDetails>> GetManyOrganizationDetailsByUserAsync(Guid userId, ProviderUserStatusType? status = null);
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
@ -43,6 +44,8 @@ public class OrganizationService : IOrganizationService
|
||||
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ILogger<OrganizationService> _logger;
|
||||
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
|
||||
public OrganizationService(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -69,7 +72,9 @@ public class OrganizationService : IOrganizationService
|
||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||
ICurrentContext currentContext,
|
||||
ILogger<OrganizationService> logger)
|
||||
ILogger<OrganizationService> logger,
|
||||
IProviderOrganizationRepository providerOrganizationRepository,
|
||||
IProviderUserRepository providerUserRepository)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -96,6 +101,8 @@ public class OrganizationService : IOrganizationService
|
||||
_organizationConnectionRepository = organizationConnectionRepository;
|
||||
_currentContext = currentContext;
|
||||
_logger = logger;
|
||||
_providerOrganizationRepository = providerOrganizationRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
}
|
||||
|
||||
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
|
||||
@ -1635,8 +1642,19 @@ public class OrganizationService : IOrganizationService
|
||||
throw new BadRequestException(failureMessage);
|
||||
}
|
||||
|
||||
var ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
|
||||
var providerOrg = await this._providerOrganizationRepository.GetByOrganizationId(organization.Id);
|
||||
|
||||
IEnumerable<string> ownerEmails;
|
||||
if (providerOrg != null)
|
||||
{
|
||||
ownerEmails = (await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId, ProviderUserStatusType.Confirmed))
|
||||
.Select(u => u.Email).Distinct();
|
||||
}
|
||||
else
|
||||
{
|
||||
ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
|
||||
OrganizationUserType.Owner)).Select(u => u.Email).Distinct();
|
||||
}
|
||||
var initialSeatCount = organization.Seats.Value;
|
||||
|
||||
await AdjustSeatsAsync(organization, seatsToAdd, prorationDate, ownerEmails);
|
||||
|
@ -84,13 +84,13 @@ public class ProviderUserRepository : Repository<ProviderUser, Guid>, IProviderU
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId)
|
||||
public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId, ProviderUserStatusType? status)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<ProviderUserUserDetails>(
|
||||
"[dbo].[ProviderUserUserDetails_ReadByProviderId]",
|
||||
new { ProviderId = providerId },
|
||||
new { ProviderId = providerId, Status = status },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
|
@ -103,7 +103,7 @@ public class ProviderUserRepository :
|
||||
return await query.FirstOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId)
|
||||
public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId, ProviderUserStatusType? status)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
@ -113,7 +113,9 @@ public class ProviderUserRepository :
|
||||
on pu.UserId equals u.Id into u_g
|
||||
from u in u_g.DefaultIfEmpty()
|
||||
select new { pu, u };
|
||||
var data = await view.Where(e => e.pu.ProviderId == providerId).Select(e => new ProviderUserUserDetails
|
||||
var data = await view
|
||||
.Where(e => e.pu.ProviderId == providerId && (status == null || e.pu.Status == status))
|
||||
.Select(e => new ProviderUserUserDetails
|
||||
{
|
||||
Id = e.pu.Id,
|
||||
UserId = e.pu.UserId,
|
||||
|
@ -41,6 +41,7 @@ public class ProviderUserOrganizationDetailsViewQuery : IQuery<ProviderUserOrgan
|
||||
PrivateKey = x.o.PrivateKey,
|
||||
ProviderId = x.p.Id,
|
||||
ProviderName = x.p.Name,
|
||||
PlanType = x.o.PlanType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
CREATE PROCEDURE [dbo].[ProviderUserUserDetails_ReadByProviderId]
|
||||
@ProviderId UNIQUEIDENTIFIER
|
||||
@ProviderId UNIQUEIDENTIFIER,
|
||||
@Status TINYINT = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@ -10,4 +11,5 @@ BEGIN
|
||||
[dbo].[ProviderUserUserDetailsView]
|
||||
WHERE
|
||||
[ProviderId] = @ProviderId
|
||||
AND [Status] = COALESCE(@Status, [Status])
|
||||
END
|
||||
|
@ -30,7 +30,8 @@ SELECT
|
||||
PU.[Type],
|
||||
PO.[ProviderId],
|
||||
PU.[Id] ProviderUserId,
|
||||
P.[Name] ProviderName
|
||||
P.[Name] ProviderName,
|
||||
O.[PlanType]
|
||||
FROM
|
||||
[dbo].[ProviderUser] PU
|
||||
INNER JOIN
|
||||
|
358
test/Api.Test/Controllers/SyncControllerTests.cs
Normal file
358
test/Api.Test/Controllers/SyncControllerTests.cs
Normal file
@ -0,0 +1,358 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using AutoFixture;
|
||||
using Bit.Api.Controllers;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Core.Models.Data;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(SyncController))]
|
||||
[SutProviderCustomize]
|
||||
public class SyncControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Get_ThrowBadRequest_WhenUserNotFound(SutProvider<SyncController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
|
||||
|
||||
async Task<SyncResponseModel> GetAction()
|
||||
{
|
||||
return await sutProvider.Sut.Get();
|
||||
}
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>((Func<Task<SyncResponseModel>>)GetAction);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Get_Success_AtLeastOneEnabledOrg(User user,
|
||||
List<List<string>> userEquivalentDomains,
|
||||
List<GlobalEquivalentDomainsType> userExcludedGlobalEquivalentDomains,
|
||||
ICollection<OrganizationUserOrganizationDetails> organizationUserDetails,
|
||||
ICollection<ProviderUserProviderDetails> providerUserDetails,
|
||||
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
|
||||
ICollection<Folder> folders,
|
||||
ICollection<CipherDetails> ciphers,
|
||||
ICollection<Send> sends,
|
||||
ICollection<Policy> policies,
|
||||
ICollection<CollectionDetails> collections,
|
||||
SutProvider<SyncController> sutProvider)
|
||||
{
|
||||
// Get dependencies
|
||||
var userService = sutProvider.GetDependency<IUserService>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
||||
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
||||
var cipherRepository = sutProvider.GetDependency<ICipherRepository>();
|
||||
var sendRepository = sutProvider.GetDependency<ISendRepository>();
|
||||
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
|
||||
var collectionRepository = sutProvider.GetDependency<ICollectionRepository>();
|
||||
var collectionCipherRepository = sutProvider.GetDependency<ICollectionCipherRepository>();
|
||||
|
||||
// Adjust random data to match required formats / test intentions
|
||||
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
|
||||
user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains);
|
||||
|
||||
// At least 1 org needs to be enabled to fully test
|
||||
if (!organizationUserDetails.Any(o => o.Enabled))
|
||||
{
|
||||
// We need at least 1 enabled org
|
||||
if (organizationUserDetails.Count > 0)
|
||||
{
|
||||
organizationUserDetails.First().Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// create an enabled org
|
||||
var enabledOrg = new Fixture().Create<OrganizationUserOrganizationDetails>();
|
||||
enabledOrg.Enabled = true;
|
||||
organizationUserDetails.Add((enabledOrg));
|
||||
}
|
||||
}
|
||||
|
||||
// Setup returns
|
||||
userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsForAnyArgs(user);
|
||||
|
||||
organizationUserRepository
|
||||
.GetManyDetailsByUserAsync(user.Id, OrganizationUserStatusType.Confirmed).Returns(organizationUserDetails);
|
||||
|
||||
providerUserRepository
|
||||
.GetManyDetailsByUserAsync(user.Id, ProviderUserStatusType.Confirmed).Returns(providerUserDetails);
|
||||
|
||||
providerUserRepository
|
||||
.GetManyOrganizationDetailsByUserAsync(user.Id, ProviderUserStatusType.Confirmed)
|
||||
.Returns(providerUserOrganizationDetails);
|
||||
|
||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
||||
|
||||
sendRepository
|
||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||
|
||||
policyRepository.GetManyByUserIdAsync(user.Id).Returns(policies);
|
||||
|
||||
// Returns for methods only called if we have enabled orgs
|
||||
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
|
||||
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
|
||||
|
||||
// Back to standard test setup
|
||||
userService.TwoFactorIsEnabledAsync(user).Returns(false);
|
||||
userService.HasPremiumFromOrganization(user).Returns(false);
|
||||
|
||||
// Execute GET
|
||||
var result = await sutProvider.Sut.Get();
|
||||
|
||||
|
||||
// Asserts
|
||||
// Assert that methods are called
|
||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||
this.AssertMethodsCalledAsync(userService, organizationUserRepository, providerUserRepository, folderRepository,
|
||||
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
||||
|
||||
Assert.IsType<SyncResponseModel>(result);
|
||||
|
||||
// Collections should not be empty when at least 1 org is enabled
|
||||
Assert.NotEmpty(result.Collections);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Get_Success_AllDisabledOrgs(User user,
|
||||
List<List<string>> userEquivalentDomains,
|
||||
List<GlobalEquivalentDomainsType> userExcludedGlobalEquivalentDomains,
|
||||
ICollection<OrganizationUserOrganizationDetails> organizationUserDetails,
|
||||
ICollection<ProviderUserProviderDetails> providerUserDetails,
|
||||
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
|
||||
ICollection<Folder> folders,
|
||||
ICollection<CipherDetails> ciphers,
|
||||
ICollection<Send> sends,
|
||||
ICollection<Policy> policies,
|
||||
SutProvider<SyncController> sutProvider)
|
||||
{
|
||||
// Get dependencies
|
||||
var userService = sutProvider.GetDependency<IUserService>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
||||
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
||||
var cipherRepository = sutProvider.GetDependency<ICipherRepository>();
|
||||
var sendRepository = sutProvider.GetDependency<ISendRepository>();
|
||||
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
|
||||
var collectionRepository = sutProvider.GetDependency<ICollectionRepository>();
|
||||
var collectionCipherRepository = sutProvider.GetDependency<ICollectionCipherRepository>();
|
||||
|
||||
// Adjust random data to match required formats / test intentions
|
||||
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
|
||||
user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains);
|
||||
|
||||
// All orgs disabled
|
||||
if (organizationUserDetails.Count > 0)
|
||||
{
|
||||
foreach (var orgUserDetails in organizationUserDetails)
|
||||
{
|
||||
orgUserDetails.Enabled = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var disabledOrg = new Fixture().Create<OrganizationUserOrganizationDetails>();
|
||||
disabledOrg.Enabled = false;
|
||||
organizationUserDetails.Add((disabledOrg));
|
||||
}
|
||||
|
||||
|
||||
// Setup returns
|
||||
userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsForAnyArgs(user);
|
||||
|
||||
organizationUserRepository
|
||||
.GetManyDetailsByUserAsync(user.Id, OrganizationUserStatusType.Confirmed).Returns(organizationUserDetails);
|
||||
|
||||
providerUserRepository
|
||||
.GetManyDetailsByUserAsync(user.Id, ProviderUserStatusType.Confirmed).Returns(providerUserDetails);
|
||||
|
||||
providerUserRepository
|
||||
.GetManyOrganizationDetailsByUserAsync(user.Id, ProviderUserStatusType.Confirmed)
|
||||
.Returns(providerUserOrganizationDetails);
|
||||
|
||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
||||
|
||||
sendRepository
|
||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||
|
||||
policyRepository.GetManyByUserIdAsync(user.Id).Returns(policies);
|
||||
|
||||
userService.TwoFactorIsEnabledAsync(user).Returns(false);
|
||||
userService.HasPremiumFromOrganization(user).Returns(false);
|
||||
|
||||
// Execute GET
|
||||
var result = await sutProvider.Sut.Get();
|
||||
|
||||
|
||||
// Asserts
|
||||
// Assert that methods are called
|
||||
|
||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||
this.AssertMethodsCalledAsync(userService, organizationUserRepository, providerUserRepository, folderRepository,
|
||||
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
||||
|
||||
Assert.IsType<SyncResponseModel>(result);
|
||||
|
||||
// Collections should be empty when all standard orgs are disabled.
|
||||
Assert.Empty(result.Collections);
|
||||
}
|
||||
|
||||
|
||||
// Test where provider org has specific plan type and assert plan type comes out on SyncResponseModel class on ProfileResponseModel
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Get_ProviderPlanTypeProperlyPopulated(User user,
|
||||
List<List<string>> userEquivalentDomains,
|
||||
List<GlobalEquivalentDomainsType> userExcludedGlobalEquivalentDomains,
|
||||
ICollection<OrganizationUserOrganizationDetails> organizationUserDetails,
|
||||
ICollection<ProviderUserProviderDetails> providerUserDetails,
|
||||
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
|
||||
ICollection<Folder> folders,
|
||||
ICollection<CipherDetails> ciphers,
|
||||
ICollection<Send> sends,
|
||||
ICollection<Policy> policies,
|
||||
ICollection<CollectionDetails> collections,
|
||||
SutProvider<SyncController> sutProvider)
|
||||
{
|
||||
// Get dependencies
|
||||
var userService = sutProvider.GetDependency<IUserService>();
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
||||
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
||||
var cipherRepository = sutProvider.GetDependency<ICipherRepository>();
|
||||
var sendRepository = sutProvider.GetDependency<ISendRepository>();
|
||||
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
|
||||
var collectionRepository = sutProvider.GetDependency<ICollectionRepository>();
|
||||
var collectionCipherRepository = sutProvider.GetDependency<ICollectionCipherRepository>();
|
||||
|
||||
// Adjust random data to match required formats / test intentions
|
||||
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
|
||||
user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains);
|
||||
|
||||
|
||||
// Setup returns
|
||||
userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsForAnyArgs(user);
|
||||
|
||||
organizationUserRepository
|
||||
.GetManyDetailsByUserAsync(user.Id, OrganizationUserStatusType.Confirmed).Returns(organizationUserDetails);
|
||||
|
||||
providerUserRepository
|
||||
.GetManyDetailsByUserAsync(user.Id, ProviderUserStatusType.Confirmed).Returns(providerUserDetails);
|
||||
|
||||
providerUserRepository
|
||||
.GetManyOrganizationDetailsByUserAsync(user.Id, ProviderUserStatusType.Confirmed)
|
||||
.Returns(providerUserOrganizationDetails);
|
||||
|
||||
folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders);
|
||||
cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers);
|
||||
|
||||
sendRepository
|
||||
.GetManyByUserIdAsync(user.Id).Returns(sends);
|
||||
|
||||
policyRepository.GetManyByUserIdAsync(user.Id).Returns(policies);
|
||||
|
||||
// Returns for methods only called if we have enabled orgs
|
||||
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
|
||||
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
|
||||
|
||||
// Back to standard test setup
|
||||
userService.TwoFactorIsEnabledAsync(user).Returns(false);
|
||||
userService.HasPremiumFromOrganization(user).Returns(false);
|
||||
|
||||
// Execute GET
|
||||
var result = await sutProvider.Sut.Get();
|
||||
|
||||
// Asserts
|
||||
// Assert that methods are called
|
||||
|
||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||
this.AssertMethodsCalledAsync(userService, organizationUserRepository, providerUserRepository, folderRepository,
|
||||
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
||||
|
||||
Assert.IsType<SyncResponseModel>(result);
|
||||
|
||||
// Look up ProviderOrg output and compare to ProviderOrg method inputs to ensure
|
||||
// product type is set correctly.
|
||||
foreach (var profProviderOrg in result.Profile.ProviderOrganizations)
|
||||
{
|
||||
var matchedProviderUserOrgDetails =
|
||||
providerUserOrganizationDetails.FirstOrDefault(p => p.OrganizationId.ToString() == profProviderOrg.Id);
|
||||
|
||||
if (matchedProviderUserOrgDetails != null)
|
||||
{
|
||||
var providerOrgProductType = StaticStore.GetPlan(matchedProviderUserOrgDetails.PlanType).Product;
|
||||
Assert.Equal(providerOrgProductType, profProviderOrg.PlanProductType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async void AssertMethodsCalledAsync(IUserService userService,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IProviderUserRepository providerUserRepository, IFolderRepository folderRepository,
|
||||
ICipherRepository cipherRepository, ISendRepository sendRepository,
|
||||
ICollectionRepository collectionRepository,
|
||||
ICollectionCipherRepository collectionCipherRepository,
|
||||
bool hasEnabledOrgs)
|
||||
{
|
||||
await userService.ReceivedWithAnyArgs(1).GetUserByPrincipalAsync(default);
|
||||
await organizationUserRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyDetailsByUserAsync(default);
|
||||
await providerUserRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyDetailsByUserAsync(default);
|
||||
await providerUserRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyOrganizationDetailsByUserAsync(default);
|
||||
|
||||
await folderRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyByUserIdAsync(default);
|
||||
|
||||
await cipherRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyByUserIdAsync(default);
|
||||
|
||||
await sendRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyByUserIdAsync(default);
|
||||
|
||||
// These two are only called when at least 1 enabled org.
|
||||
if (hasEnabledOrgs)
|
||||
{
|
||||
await collectionRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyByUserIdAsync(default);
|
||||
await collectionCipherRepository.ReceivedWithAnyArgs(1)
|
||||
.GetManyByUserIdAsync(default);
|
||||
}
|
||||
else
|
||||
{
|
||||
// all disabled orgs
|
||||
await collectionRepository.ReceivedWithAnyArgs(0)
|
||||
.GetManyByUserIdAsync(default);
|
||||
await collectionCipherRepository.ReceivedWithAnyArgs(0)
|
||||
.GetManyByUserIdAsync(default);
|
||||
}
|
||||
|
||||
await userService.ReceivedWithAnyArgs(1)
|
||||
.TwoFactorIsEnabledAsync(default);
|
||||
await userService.ReceivedWithAnyArgs(1)
|
||||
.HasPremiumFromOrganization(default);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
-- SG-992 changes: add planType to provider orgs
|
||||
CREATE OR ALTER VIEW [dbo].[ProviderUserProviderOrganizationDetailsView]
|
||||
AS
|
||||
SELECT
|
||||
PU.[UserId],
|
||||
PO.[OrganizationId],
|
||||
O.[Name],
|
||||
O.[Enabled],
|
||||
O.[UsePolicies],
|
||||
O.[UseSso],
|
||||
O.[UseKeyConnector],
|
||||
O.[UseScim],
|
||||
O.[UseGroups],
|
||||
O.[UseDirectory],
|
||||
O.[UseEvents],
|
||||
O.[UseTotp],
|
||||
O.[Use2fa],
|
||||
O.[UseApi],
|
||||
O.[UseResetPassword],
|
||||
O.[SelfHost],
|
||||
O.[UsersGetPremium],
|
||||
O.[UseCustomPermissions],
|
||||
O.[Seats],
|
||||
O.[MaxCollections],
|
||||
O.[MaxStorageGb],
|
||||
O.[Identifier],
|
||||
PO.[Key],
|
||||
O.[PublicKey],
|
||||
O.[PrivateKey],
|
||||
PU.[Status],
|
||||
PU.[Type],
|
||||
PO.[ProviderId],
|
||||
PU.[Id] ProviderUserId,
|
||||
P.[Name] ProviderName,
|
||||
O.[PlanType] -- new prop
|
||||
FROM
|
||||
[dbo].[ProviderUser] PU
|
||||
INNER JOIN
|
||||
[dbo].[ProviderOrganization] PO ON PO.[ProviderId] = PU.[ProviderId]
|
||||
INNER JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = PO.[OrganizationId]
|
||||
INNER JOIN
|
||||
[dbo].[Provider] P ON P.[Id] = PU.[ProviderId]
|
||||
GO
|
||||
|
||||
|
||||
-- Refresh metadata of stored procs & functions that use the updated view
|
||||
IF OBJECT_ID('[dbo].[ProviderUserProviderOrganizationDetails_ReadByUserIdStatus]') IS NOT NULL
|
||||
BEGIN
|
||||
EXECUTE sp_refreshsqlmodule N'[dbo].[ProviderUserProviderOrganizationDetails_ReadByUserIdStatus]';
|
||||
END
|
||||
GO
|
||||
|
||||
|
||||
-- EC-591 / SG-996 changes: add optional status to stored proc
|
||||
CREATE OR ALTER PROCEDURE [dbo].[ProviderUserUserDetails_ReadByProviderId]
|
||||
@ProviderId UNIQUEIDENTIFIER,
|
||||
@Status TINYINT = NULL -- new: this is required to be backwards compatible
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
[dbo].[ProviderUserUserDetailsView]
|
||||
WHERE
|
||||
[ProviderId] = @ProviderId
|
||||
AND [Status] = COALESCE(@Status, [Status]) -- new
|
||||
END
|
||||
GO
|
Loading…
Reference in New Issue
Block a user