diff --git a/src/Api/Controllers/OrganizationUsersController.cs b/src/Api/Controllers/OrganizationUsersController.cs index 07218e4e3a..fed8cf252d 100644 --- a/src/Api/Controllers/OrganizationUsersController.cs +++ b/src/Api/Controllers/OrganizationUsersController.cs @@ -58,7 +58,8 @@ namespace Bit.Api.Controllers public async Task Invite(string orgId, [FromBody]OrganizationUserInviteRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); - var result = await _organizationService.InviteUserAsync(new Guid(orgId), model.Email); + var result = await _organizationService.InviteUserAsync(new Guid(orgId), model.Email, + model.Subvaults.Select(s => s.ToSubvaultUser())); } [HttpPut("{id}/accept")] diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationUserRequestModels.cs b/src/Core/Models/Api/Request/Organizations/OrganizationUserRequestModels.cs index 24a4f37579..5e0edad031 100644 --- a/src/Core/Models/Api/Request/Organizations/OrganizationUserRequestModels.cs +++ b/src/Core/Models/Api/Request/Organizations/OrganizationUserRequestModels.cs @@ -7,6 +7,7 @@ namespace Bit.Core.Models.Api public class OrganizationUserInviteRequestModel { public string Email { get; set; } + public IEnumerable Subvaults { get; set; } } public class OrganizationUserAcceptRequestModel @@ -22,31 +23,35 @@ namespace Bit.Core.Models.Api public class OrganizationUserUpdateRequestModel { public Enums.OrganizationUserType Type { get; set; } - public IEnumerable Subvaults { get; set; } + public IEnumerable Subvaults { get; set; } + } - public class Subvault + public class OrganizationUserSubvaultRequestModel + { + public string Id { get; set; } + public string SubvaultId { get; set; } + public bool Admin { get; set; } + public bool ReadOnly { get; set; } + + public SubvaultUser ToSubvaultUser() { - public string Id { get; set; } - public string SubvaultId { get; set; } - public bool Admin { get; set; } - public bool ReadOnly { get; set; } - - public SubvaultUser ToSubvaultUser() + var subvault = new SubvaultUser { - var user = new SubvaultUser - { - SubvaultId = new Guid(SubvaultId), - Admin = Admin, - ReadOnly = ReadOnly - }; + Admin = Admin, + ReadOnly = ReadOnly + }; - if(string.IsNullOrWhiteSpace(Id)) - { - user.Id = new Guid(Id); - } - - return user; + if(!string.IsNullOrWhiteSpace(SubvaultId)) + { + subvault.SubvaultId = new Guid(SubvaultId); } + + if(!string.IsNullOrWhiteSpace(Id)) + { + subvault.Id = new Guid(Id); + } + + return subvault; } } } diff --git a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs index c3dc942822..7e02a682d5 100644 --- a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs @@ -2,7 +2,6 @@ using Bit.Core.Enums; using Bit.Core.Models.Data; using System.Collections.Generic; -using Bit.Core.Models.Table; using System.Linq; namespace Bit.Core.Models.Api @@ -36,12 +35,13 @@ namespace Bit.Core.Models.Api public class OrganizationUserDetailsResponseModel : OrganizationUserResponseModel { public OrganizationUserDetailsResponseModel(OrganizationUserUserDetails organizationUser, - IEnumerable subvaults) + IEnumerable subvaults) : base(organizationUser, "organizationUserDetails") { - Subvaults = new ListResponseModel(subvaults.Select(s => new SubvaultResponseModel(s))); + Subvaults = new ListResponseModel( + subvaults.Select(s => new OrganizationUserSubvaultResponseModel(s))); } - public ListResponseModel Subvaults { get; set; } + public ListResponseModel Subvaults { get; set; } } } diff --git a/src/Core/Models/Api/Response/OrganizationUserSubvaultResponseModel.cs b/src/Core/Models/Api/Response/OrganizationUserSubvaultResponseModel.cs new file mode 100644 index 0000000000..9d083c2bdb --- /dev/null +++ b/src/Core/Models/Api/Response/OrganizationUserSubvaultResponseModel.cs @@ -0,0 +1,30 @@ +using System; +using Bit.Core.Models.Data; + +namespace Bit.Core.Models.Api +{ + public class OrganizationUserSubvaultResponseModel : ResponseModel + { + public OrganizationUserSubvaultResponseModel(SubvaultUserDetails details, + string obj = "organizationUserSubvault") + : base(obj) + { + if(details == null) + { + throw new ArgumentNullException(nameof(details)); + } + + Id = details.Id.ToString(); + Name = details.Name; + SubvaultId = details.SubvaultId.ToString(); + ReadOnly = details.ReadOnly; + Admin = details.Admin; + } + + public string Id { get; set; } + public string Name { get; set; } + public string SubvaultId { get; set; } + public bool ReadOnly { get; set; } + public bool Admin { get; set; } + } +} diff --git a/src/Core/Models/Data/SubvaultUserDetails.cs b/src/Core/Models/Data/SubvaultUserDetails.cs new file mode 100644 index 0000000000..71db9a3fd8 --- /dev/null +++ b/src/Core/Models/Data/SubvaultUserDetails.cs @@ -0,0 +1,14 @@ +using System; + +namespace Bit.Core.Models.Data +{ + public class SubvaultUserDetails + { + public Guid Id { get; set; } + public Guid OrganizationUserId { get; set; } + public string Name { get; set; } + public Guid SubvaultId { get; set; } + public bool ReadOnly { get; set; } + public bool Admin { get; set; } + } +} diff --git a/src/Core/Repositories/IOrganizationUserRepository.cs b/src/Core/Repositories/IOrganizationUserRepository.cs index dd3516ba97..b117d437f1 100644 --- a/src/Core/Repositories/IOrganizationUserRepository.cs +++ b/src/Core/Repositories/IOrganizationUserRepository.cs @@ -9,7 +9,7 @@ namespace Bit.Core.Repositories public interface IOrganizationUserRepository : IRepository { Task GetByOrganizationAsync(Guid organizationId, Guid userId); - Task>> GetDetailsByIdAsync(Guid id); + Task>> GetDetailsByIdAsync(Guid id); Task> GetManyDetailsByOrganizationAsync(Guid organizationId); Task> GetManyDetailsByUserAsync(Guid userId); } diff --git a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs index e7c969526a..3a45fb54f4 100644 --- a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs +++ b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs @@ -33,7 +33,7 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task>> GetDetailsByIdAsync(Guid id) + public async Task>> GetDetailsByIdAsync(Guid id) { using(var connection = new SqlConnection(ConnectionString)) { @@ -43,8 +43,8 @@ namespace Bit.Core.Repositories.SqlServer commandType: CommandType.StoredProcedure); var user = (await results.ReadAsync()).SingleOrDefault(); - var subvaults = (await results.ReadAsync()).ToList(); - return new Tuple>(user, subvaults); + var subvaults = (await results.ReadAsync()).ToList(); + return new Tuple>(user, subvaults); } } diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index 95f7997817..10c9e52640 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -9,9 +9,9 @@ namespace Bit.Core.Services public interface IOrganizationService { Task> SignUpAsync(OrganizationSignup organizationSignup); - Task InviteUserAsync(Guid organizationId, string email); + Task InviteUserAsync(Guid organizationId, string email, IEnumerable subvaults); Task AcceptUserAsync(Guid organizationUserId, User user, string token); Task ConfirmUserAsync(Guid organizationUserId, string key); - Task SaveUserAsync(OrganizationUser user, IEnumerable subvaults); + Task SaveUserAsync(OrganizationUser user, IEnumerable subvaults); } } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 8a61bb0c9a..338d71f250 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -90,7 +90,8 @@ namespace Bit.Core.Services } } - public async Task InviteUserAsync(Guid organizationId, string email) + public async Task InviteUserAsync(Guid organizationId, string email, + IEnumerable subvaults) { var orgUser = new OrganizationUser { @@ -105,6 +106,7 @@ namespace Bit.Core.Services }; await _organizationUserRepository.CreateAsync(orgUser); + await SaveUserSubvaultsAsync(orgUser, subvaults, true); // TODO: send email @@ -149,7 +151,7 @@ namespace Bit.Core.Services return orgUser; } - public async Task SaveUserAsync(OrganizationUser user, IEnumerable subvaults) + public async Task SaveUserAsync(OrganizationUser user, IEnumerable subvaults) { if(user.Id.Equals(default(Guid))) { @@ -157,28 +159,36 @@ namespace Bit.Core.Services } await _organizationUserRepository.ReplaceAsync(user); + await SaveUserSubvaultsAsync(user, subvaults, false); + } + private async Task SaveUserSubvaultsAsync(OrganizationUser user, IEnumerable subvaults, bool newUser) + { var orgSubvaults = await _subvaultRepository.GetManyByOrganizationIdAsync(user.OrganizationId); - var currentUserSubvaults = await _subvaultUserRepository.GetManyByOrganizationUserIdAsync(user.Id); + var currentUserSubvaults = newUser ? null : await _subvaultUserRepository.GetManyByOrganizationUserIdAsync(user.Id); // Let's make sure all these belong to this user and organization. - var filteredSubvaults = subvaults.Where(s => - orgSubvaults.Any(os => os.Id == s.SubvaultId) && - (s.Id == default(Guid) || currentUserSubvaults.Any(cs => cs.Id == s.Id))); - - var subvaultsToDelete = currentUserSubvaults.Where(cs => !subvaults.Any(s => s.Id == cs.Id)); + var filteredSubvaults = subvaults.Where(s => orgSubvaults.Any(os => os.Id == s.SubvaultId)); + if(!newUser) + { + filteredSubvaults = filteredSubvaults.Where(s => + s.Id == default(Guid) || currentUserSubvaults.Any(cs => cs.Id == s.Id)); + } foreach(var subvault in filteredSubvaults) { + subvault.OrganizationUserId = user.Id; await _subvaultUserRepository.UpsertAsync(subvault); } - foreach(var subvault in subvaultsToDelete) + if(!newUser) { - await _subvaultUserRepository.DeleteAsync(subvault); + var subvaultsToDelete = currentUserSubvaults.Where(cs => !subvaults.Any(s => s.Id == cs.Id)); + foreach(var subvault in subvaultsToDelete) + { + await _subvaultUserRepository.DeleteAsync(subvault); + } } - - return user; } } } diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 43f3f923e6..f2d93b6628 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -156,7 +156,8 @@ - + + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadById.sql b/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadById.sql index cd8d878daa..46f9173c8e 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadById.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadById.sql @@ -12,11 +12,9 @@ BEGIN [Id] = @Id SELECT - S.* + * FROM - [dbo].[SubvaultView] S - INNER JOIN - [dbo].[SubvaultUser] SU ON SU.[SubvaultId] = S.[Id] + [dbo].[SubvaultUserDetailsView] WHERE - SU.[OrganizationUserId] = @Id + [OrganizationUserId] = @Id END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Subvault_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/SubvaultUserDetails_ReadByUserId.sql similarity index 55% rename from src/Sql/dbo/Stored Procedures/Subvault_ReadByUserId.sql rename to src/Sql/dbo/Stored Procedures/SubvaultUserDetails_ReadByUserId.sql index 9fa2e6db9b..b4f6adec56 100644 --- a/src/Sql/dbo/Stored Procedures/Subvault_ReadByUserId.sql +++ b/src/Sql/dbo/Stored Procedures/SubvaultUserDetails_ReadByUserId.sql @@ -1,15 +1,13 @@ -CREATE PROCEDURE [dbo].[Subvault_ReadByUserId] +CREATE PROCEDURE [dbo].[SubvaultUserDetails_ReadByUserId] @UserId UNIQUEIDENTIFIER AS BEGIN SET NOCOUNT ON SELECT - S.* + SU.* FROM - [dbo].[SubvaultView] S - INNER JOIN - [SubvaultUser] SU ON SU.[SubvaultId] = S.[Id] + [dbo].[SubvaultUserDetailsView] SU INNER JOIN [OrganizationUser] OU ON SU.[OrganizationUserId] = OU.[Id] WHERE diff --git a/src/Sql/dbo/Views/SubvaultUserDetailsView.sql b/src/Sql/dbo/Views/SubvaultUserDetailsView.sql new file mode 100644 index 0000000000..0136c7c58e --- /dev/null +++ b/src/Sql/dbo/Views/SubvaultUserDetailsView.sql @@ -0,0 +1,13 @@ +CREATE VIEW [dbo].[SubvaultUserDetailsView] +AS +SELECT + SU.[Id], + SU.[OrganizationUserId], + S.[Name], + S.[Id] SubvaultId, + SU.[ReadOnly], + SU.[Admin] +FROM + [dbo].[SubvaultUser] SU +INNER JOIN + [dbo].[Subvault] S ON S.[Id] = SU.[SubvaultId] \ No newline at end of file