mirror of
https://github.com/bitwarden/server.git
synced 2024-11-25 12:45:18 +01:00
[AC-621] Added possibility of adding users through SCIM to an Organization without a confirmed Owner (#2846)
* [AC-621] Added possibility of adding users through SCIM to an Organization without a confirmed Owner * [AC-621] Passing EventSystemUser argument for HasConfirmedOwnersExceptAsync in user delete actions by SCIM * [AC-624] Removed EventSystemUser parameter from IOrganizationService.HasConfirmedOwnersExceptAsync * [AC-621] Added IProviderUserRepository.GetManyOrganizationDetailsByOrganizationAsync * [AC-621] Updated OrganizationService.HasConfirmedOwnersExceptAsync to use IProviderUserRepository.GetManyOrganizationDetailsByOrganizationAsync to check for any confirmed provider users * [AC-621] Removed unused EventSystemUser parameters * [AC-621] Refactored ProviderUserRepository.GetManyByOrganizationAsync to return ProviderUser objects * [AC-621] Removed default parameter value for Status
This commit is contained in:
parent
db8e82ff03
commit
04e18ee8e7
@ -18,4 +18,5 @@ public interface IProviderUserRepository : IRepository<ProviderUser, Guid>
|
|||||||
Task DeleteManyAsync(IEnumerable<Guid> userIds);
|
Task DeleteManyAsync(IEnumerable<Guid> userIds);
|
||||||
Task<IEnumerable<ProviderUserPublicKey>> GetManyPublicKeysByProviderUserAsync(Guid providerId, IEnumerable<Guid> Ids);
|
Task<IEnumerable<ProviderUserPublicKey>> GetManyPublicKeysByProviderUserAsync(Guid providerId, IEnumerable<Guid> Ids);
|
||||||
Task<int> GetCountByOnlyOwnerAsync(Guid userId);
|
Task<int> GetCountByOnlyOwnerAsync(Guid userId);
|
||||||
|
Task<ICollection<ProviderUser>> GetManyByOrganizationAsync(Guid organizationId, ProviderUserStatusType? status = null);
|
||||||
}
|
}
|
||||||
|
@ -993,7 +993,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (organizationUsers, events) = await SaveUsersSendInvitesAsync(organizationId, invites);
|
var (organizationUsers, events) = await SaveUsersSendInvitesAsync(organizationId, invites, systemUser: null);
|
||||||
|
|
||||||
await _eventService.LogOrganizationUserEventsAsync(events);
|
await _eventService.LogOrganizationUserEventsAsync(events);
|
||||||
|
|
||||||
@ -1003,7 +1003,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, EventSystemUser systemUser,
|
public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, EventSystemUser systemUser,
|
||||||
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
||||||
{
|
{
|
||||||
var (organizationUsers, events) = await SaveUsersSendInvitesAsync(organizationId, invites);
|
var (organizationUsers, events) = await SaveUsersSendInvitesAsync(organizationId, invites, systemUser);
|
||||||
|
|
||||||
await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, systemUser, e.Item3)));
|
await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, systemUser, e.Item3)));
|
||||||
|
|
||||||
@ -1011,7 +1011,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(List<OrganizationUser> organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> SaveUsersSendInvitesAsync(Guid organizationId,
|
private async Task<(List<OrganizationUser> organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> SaveUsersSendInvitesAsync(Guid organizationId,
|
||||||
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, EventSystemUser? systemUser)
|
||||||
{
|
{
|
||||||
var organization = await GetOrgById(organizationId);
|
var organization = await GetOrgById(organizationId);
|
||||||
var initialSeatCount = organization.Seats;
|
var initialSeatCount = organization.Seats;
|
||||||
@ -1040,7 +1040,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var invitedAreAllOwners = invites.All(i => i.invite.Type == OrganizationUserType.Owner);
|
var invitedAreAllOwners = invites.All(i => i.invite.Type == OrganizationUserType.Owner);
|
||||||
if (!invitedAreAllOwners && !await HasConfirmedOwnersExceptAsync(organizationId, new Guid[] { }))
|
if (!invitedAreAllOwners && !await HasConfirmedOwnersExceptAsync(organizationId, new Guid[] { }, includeProvider: true))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
||||||
}
|
}
|
||||||
@ -1596,7 +1596,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
throw new BadRequestException("Only owners can delete other owners.");
|
throw new BadRequestException("Only owners can delete other owners.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await HasConfirmedOwnersExceptAsync(organizationId, new[] { organizationUserId }))
|
if (!await HasConfirmedOwnersExceptAsync(organizationId, new[] { organizationUserId }, includeProvider: true))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
||||||
}
|
}
|
||||||
@ -1700,7 +1700,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
bool hasOtherOwner = confirmedOwnersIds.Except(organizationUsersId).Any();
|
bool hasOtherOwner = confirmedOwnersIds.Except(organizationUsersId).Any();
|
||||||
if (!hasOtherOwner && includeProvider)
|
if (!hasOtherOwner && includeProvider)
|
||||||
{
|
{
|
||||||
return (await _currentContext.ProviderIdForOrg(organizationId)).HasValue;
|
return (await _providerUserRepository.GetManyByOrganizationAsync(organizationId, ProviderUserStatusType.Confirmed)).Any();
|
||||||
}
|
}
|
||||||
return hasOtherOwner;
|
return hasOtherOwner;
|
||||||
}
|
}
|
||||||
@ -2272,7 +2272,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
throw new BadRequestException("Already revoked.");
|
throw new BadRequestException("Already revoked.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, new[] { organizationUser.Id }))
|
if (!await HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, new[] { organizationUser.Id }, includeProvider: true))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
||||||
}
|
}
|
||||||
|
@ -160,4 +160,17 @@ public class ProviderUserRepository : Repository<ProviderUser, Guid>, IProviderU
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ICollection<ProviderUser>> GetManyByOrganizationAsync(Guid organizationId, ProviderUserStatusType? status = null)
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.QueryAsync<ProviderUser>(
|
||||||
|
"[dbo].[ProviderUser_ReadByOrganizationIdStatus]",
|
||||||
|
new { OrganizationId = organizationId, Status = status },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,4 +180,19 @@ public class ProviderUserRepository :
|
|||||||
.CountAsync();
|
.CountAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ICollection<ProviderUser>> GetManyByOrganizationAsync(Guid organizationId, ProviderUserStatusType? status = null)
|
||||||
|
{
|
||||||
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
var query = from pu in dbContext.ProviderUsers
|
||||||
|
join po in dbContext.ProviderOrganizations
|
||||||
|
on pu.ProviderId equals po.ProviderId
|
||||||
|
where po.OrganizationId == organizationId &&
|
||||||
|
(status == null || pu.Status == status)
|
||||||
|
select pu;
|
||||||
|
return await query.ToArrayAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[ProviderUser_ReadByOrganizationIdStatus]
|
||||||
|
@OrganizationId UNIQUEIDENTIFIER,
|
||||||
|
@Status TINYINT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
PU.*
|
||||||
|
FROM
|
||||||
|
[dbo].[ProviderUserView] PU
|
||||||
|
INNER JOIN [dbo].[ProviderOrganizationView] as PO
|
||||||
|
ON PU.[ProviderId] = PO.[ProviderId]
|
||||||
|
WHERE
|
||||||
|
PO.[OrganizationId] = @OrganizationId
|
||||||
|
AND (@Status IS NULL OR PU.[Status] = @Status)
|
||||||
|
END
|
@ -6,7 +6,9 @@ using Bit.Core.Auth.Models.Data;
|
|||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Entities.Provider;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Enums.Provider;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
@ -1415,4 +1417,56 @@ public class OrganizationServiceTests
|
|||||||
await eventService.Received()
|
await eventService.Received()
|
||||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, eventSystemUser);
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, eventSystemUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HasConfirmedOwnersExcept_WithConfirmedOwner_ReturnsTrue(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
|
||||||
|
.Returns(new List<OrganizationUser> { owner });
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), true);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HasConfirmedOwnersExcept_ExcludingConfirmedOwner_ReturnsFalse(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
|
||||||
|
.Returns(new List<OrganizationUser> { owner });
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid> { owner.Id }, true);
|
||||||
|
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HasConfirmedOwnersExcept_WithInvitedOwner_ReturnsFalse(Organization organization, [OrganizationUser(OrganizationUserStatusType.Invited, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
|
||||||
|
.Returns(new List<OrganizationUser> { owner });
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), true);
|
||||||
|
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(true)]
|
||||||
|
[BitAutoData(false)]
|
||||||
|
public async Task HasConfirmedOwnersExcept_WithConfirmedProviderUser_IncludeProviderTrue_ReturnsTrue(bool includeProvider, Organization organization, ProviderUser providerUser, SutProvider<OrganizationService> sutProvider)
|
||||||
|
{
|
||||||
|
providerUser.Status = ProviderUserStatusType.Confirmed;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IProviderUserRepository>()
|
||||||
|
.GetManyByOrganizationAsync(organization.Id, ProviderUserStatusType.Confirmed)
|
||||||
|
.Returns(new List<ProviderUser> { providerUser });
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), includeProvider);
|
||||||
|
|
||||||
|
Assert.Equal(includeProvider, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
CREATE OR ALTER PROCEDURE [dbo].[ProviderUser_ReadByOrganizationIdStatus]
|
||||||
|
@OrganizationId UNIQUEIDENTIFIER,
|
||||||
|
@Status TINYINT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
PU.*
|
||||||
|
FROM
|
||||||
|
[dbo].[ProviderUserView] PU
|
||||||
|
INNER JOIN [dbo].[ProviderOrganizationView] as PO
|
||||||
|
ON PU.[ProviderId] = PO.[ProviderId]
|
||||||
|
WHERE
|
||||||
|
PO.[OrganizationId] = @OrganizationId
|
||||||
|
AND (@Status IS NULL OR PU.[Status] = @Status)
|
||||||
|
END
|
||||||
|
GO
|
Loading…
Reference in New Issue
Block a user