From 95fdfeb51952185dd2507c794063d8dc17ee1cf5 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 29 Mar 2017 21:26:19 -0400 Subject: [PATCH] orgs must have one owner checks --- .../IOrganizationUserRepository.cs | 1 + .../SqlServer/OrganizationUserRepository.cs | 16 ++++++++++++- .../Implementations/OrganizationService.cs | 24 +++++++++++++++++-- src/Sql/Sql.sqlproj | 1 + .../OrganizationUser_ReadByOrganizationId.sql | 15 ++++++++++++ 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationId.sql diff --git a/src/Core/Repositories/IOrganizationUserRepository.cs b/src/Core/Repositories/IOrganizationUserRepository.cs index efb151b172..8595fa04a4 100644 --- a/src/Core/Repositories/IOrganizationUserRepository.cs +++ b/src/Core/Repositories/IOrganizationUserRepository.cs @@ -10,6 +10,7 @@ namespace Bit.Core.Repositories public interface IOrganizationUserRepository : IRepository { Task GetByOrganizationAsync(Guid organizationId, Guid userId); + Task> GetManyByOrganizationAsync(Guid organizationId, OrganizationUserType? type); Task GetByOrganizationAsync(Guid organizationId, string email); Task>> GetDetailsByIdAsync(Guid id); Task> GetManyDetailsByOrganizationAsync(Guid organizationId); diff --git a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs index 6c0a2752a9..a1ccff874b 100644 --- a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs +++ b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs @@ -47,6 +47,20 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task> GetManyByOrganizationAsync(Guid organizationId, + OrganizationUserType? type) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[OrganizationUser_ReadByOrganizationId]", + new { OrganizationId = organizationId, Type = type }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + public async Task>> GetDetailsByIdAsync(Guid id) { using(var connection = new SqlConnection(ConnectionString)) @@ -75,7 +89,7 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task> GetManyDetailsByUserAsync(Guid userId, + public async Task> GetManyDetailsByUserAsync(Guid userId, OrganizationUserStatusType? status = null) { using(var connection = new SqlConnection(ConnectionString)) diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 70171ef9f7..9cdbf62ec0 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -242,10 +242,17 @@ namespace Bit.Core.Services throw new BadRequestException("Cannot update users."); } - // TODO: validate subvaults? + var confirmedOwners = (await GetConfirmedOwnersAsync(user.OrganizationId)).ToList(); + if(confirmedOwners.Count == 1 && confirmedOwners[0].Id == user.Id) + { + throw new BadRequestException("Organization must have at least one confirmed owner."); + } + + var orgSubvaults = await _subvaultRepository.GetManyByOrganizationIdAsync(user.OrganizationId); + var filteredSubvaults = subvaults.Where(s => orgSubvaults.Any(os => os.Id == s.SubvaultId)); await _organizationUserRepository.ReplaceAsync(user); - await SaveUserSubvaultsAsync(user, subvaults, false); + await SaveUserSubvaultsAsync(user, filteredSubvaults, false); } public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId) @@ -261,9 +268,22 @@ namespace Bit.Core.Services throw new BadRequestException("User not valid."); } + var confirmedOwners = (await GetConfirmedOwnersAsync(organizationId)).ToList(); + if(confirmedOwners.Count == 1 && confirmedOwners[0].Id == organizationUserId) + { + throw new BadRequestException("Organization must have at least one confirmed owner."); + } + await _organizationUserRepository.DeleteAsync(orgUser); } + private async Task> GetConfirmedOwnersAsync(Guid organizationId) + { + var owners = await _organizationUserRepository.GetManyByOrganizationAsync(organizationId, + Enums.OrganizationUserType.Owner); + return owners.Where(o => o.Status == Enums.OrganizationUserStatusType.Confirmed); + } + private async Task OrganizationUserHasAdminRightsAsync(Guid organizationId, Guid userId) { var orgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId); diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index e244caef65..f7d0131cc6 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -178,5 +178,6 @@ + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationId.sql new file mode 100644 index 0000000000..9e73327dc4 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationId.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationUserView] + WHERE + [OrganizationId] = @OrganizationId + AND (@Type IS NULL OR [Type] = @Type) +END \ No newline at end of file