1
0
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:
Rui Tomé 2023-05-17 16:39:08 +01:00 committed by GitHub
parent db8e82ff03
commit 04e18ee8e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 7 deletions

View File

@ -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);
} }

View File

@ -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.");
} }

View File

@ -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();
}
}
} }

View File

@ -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();
}
}
} }

View File

@ -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

View File

@ -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);
}
} }

View File

@ -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