diff --git a/src/Api/Controllers/SubvaultUsersController.cs b/src/Api/Controllers/SubvaultUsersController.cs new file mode 100644 index 0000000000..6349b5144d --- /dev/null +++ b/src/Api/Controllers/SubvaultUsersController.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Bit.Core.Repositories; +using Microsoft.AspNetCore.Authorization; +using Bit.Core.Models.Api; +using Bit.Core.Exceptions; +using Bit.Core.Services; + +namespace Bit.Api.Controllers +{ + [Route("organizations/{orgId}/subvaultUsers")] + [Authorize("Application")] + public class SubvaultUsersController : Controller + { + private readonly ISubvaultRepository _subvaultRepository; + private readonly ISubvaultUserRepository _subvaultUserRepository; + private readonly IUserService _userService; + + public SubvaultUsersController( + ISubvaultRepository subvaultRepository, + ISubvaultUserRepository subvaultUserRepository, + IUserService userService) + { + _subvaultRepository = subvaultRepository; + _subvaultUserRepository = subvaultUserRepository; + _userService = userService; + } + + [HttpGet("{subvaultId}")] + public async Task> GetBySubvault(string orgId, string subvaultId) + { + // TODO: permission check + var subvaultUsers = await _subvaultUserRepository.GetManyDetailsBySubvaultIdAsync(new Guid(subvaultId)); + var responses = subvaultUsers.Select(s => new SubvaultUserResponseModel(s)); + return new ListResponseModel(responses); + } + + [HttpDelete("{id}")] + [HttpPost("{id}/delete")] + public async Task Delete(string orgId, string id) + { + var user = await _subvaultUserRepository.GetByIdAsync(new Guid(id)); + if(user == null) + { + throw new NotFoundException(); + } + + // TODO: permission check + await _subvaultUserRepository.DeleteAsync(user); + } + } +} diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 942a529a62..a04d21909d 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -198,6 +198,7 @@ namespace Bit.Api services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Cors services.AddCors(config => diff --git a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs index 7e02a682d5..43f0ad0de1 100644 --- a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs @@ -35,7 +35,7 @@ namespace Bit.Core.Models.Api public class OrganizationUserDetailsResponseModel : OrganizationUserResponseModel { public OrganizationUserDetailsResponseModel(OrganizationUserUserDetails organizationUser, - IEnumerable subvaults) + IEnumerable subvaults) : base(organizationUser, "organizationUserDetails") { Subvaults = new ListResponseModel( diff --git a/src/Core/Models/Api/Response/OrganizationUserSubvaultResponseModel.cs b/src/Core/Models/Api/Response/OrganizationUserSubvaultResponseModel.cs index 9d083c2bdb..cbce095107 100644 --- a/src/Core/Models/Api/Response/OrganizationUserSubvaultResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationUserSubvaultResponseModel.cs @@ -5,7 +5,7 @@ namespace Bit.Core.Models.Api { public class OrganizationUserSubvaultResponseModel : ResponseModel { - public OrganizationUserSubvaultResponseModel(SubvaultUserDetails details, + public OrganizationUserSubvaultResponseModel(SubvaultUserSubvaultDetails details, string obj = "organizationUserSubvault") : base(obj) { diff --git a/src/Core/Models/Api/Response/SubvaultUserResponseModel.cs b/src/Core/Models/Api/Response/SubvaultUserResponseModel.cs new file mode 100644 index 0000000000..72e6247301 --- /dev/null +++ b/src/Core/Models/Api/Response/SubvaultUserResponseModel.cs @@ -0,0 +1,39 @@ +using System; +using Bit.Core.Models.Table; +using Bit.Core.Models.Data; +using Bit.Core.Enums; + +namespace Bit.Core.Models.Api +{ + public class SubvaultUserResponseModel : ResponseModel + { + public SubvaultUserResponseModel(SubvaultUserUserDetails subvaultUser) + : base("subvaultUser") + { + if(subvaultUser == null) + { + throw new ArgumentNullException(nameof(subvaultUser)); + } + + Id = subvaultUser.Id.ToString(); + OrganizationUserId = subvaultUser.OrganizationUserId.ToString(); + SubvaultId = subvaultUser.SubvaultId.ToString(); + Name = subvaultUser.Name; + Email = subvaultUser.Email; + Type = subvaultUser.Type; + Status = subvaultUser.Status; + ReadOnly = subvaultUser.ReadOnly; + Admin = subvaultUser.Admin; + } + + public string Id { get; set; } + public string OrganizationUserId { get; set; } + public string SubvaultId { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public OrganizationUserType Type { get; set; } + public OrganizationUserStatusType Status { 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/SubvaultUserSubvaultDetails.cs similarity index 87% rename from src/Core/Models/Data/SubvaultUserDetails.cs rename to src/Core/Models/Data/SubvaultUserSubvaultDetails.cs index 71db9a3fd8..404b7cdfa6 100644 --- a/src/Core/Models/Data/SubvaultUserDetails.cs +++ b/src/Core/Models/Data/SubvaultUserSubvaultDetails.cs @@ -2,7 +2,7 @@ namespace Bit.Core.Models.Data { - public class SubvaultUserDetails + public class SubvaultUserSubvaultDetails { public Guid Id { get; set; } public Guid OrganizationUserId { get; set; } diff --git a/src/Core/Models/Data/SubvaultUserUserDetails.cs b/src/Core/Models/Data/SubvaultUserUserDetails.cs new file mode 100644 index 0000000000..d21fecb5b4 --- /dev/null +++ b/src/Core/Models/Data/SubvaultUserUserDetails.cs @@ -0,0 +1,17 @@ +using System; + +namespace Bit.Core.Models.Data +{ + public class SubvaultUserUserDetails + { + public Guid Id { get; set; } + public Guid OrganizationUserId { get; set; } + public Guid SubvaultId { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public Enums.OrganizationUserStatusType Status { get; set; } + public Enums.OrganizationUserType Type { 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 8595fa04a4..427288d9c4 100644 --- a/src/Core/Repositories/IOrganizationUserRepository.cs +++ b/src/Core/Repositories/IOrganizationUserRepository.cs @@ -12,7 +12,7 @@ namespace Bit.Core.Repositories Task GetByOrganizationAsync(Guid organizationId, Guid userId); Task> GetManyByOrganizationAsync(Guid organizationId, OrganizationUserType? type); Task GetByOrganizationAsync(Guid organizationId, string email); - Task>> GetDetailsByIdAsync(Guid id); + Task>> GetDetailsByIdAsync(Guid id); Task> GetManyDetailsByOrganizationAsync(Guid organizationId); Task> GetManyDetailsByUserAsync(Guid userId, OrganizationUserStatusType? status = null); diff --git a/src/Core/Repositories/ISubvaultUserRepository.cs b/src/Core/Repositories/ISubvaultUserRepository.cs index 17f603b380..38cd5042cc 100644 --- a/src/Core/Repositories/ISubvaultUserRepository.cs +++ b/src/Core/Repositories/ISubvaultUserRepository.cs @@ -9,7 +9,8 @@ namespace Bit.Core.Repositories public interface ISubvaultUserRepository : IRepository { Task> GetManyByOrganizationUserIdAsync(Guid orgUserId); - Task> GetManyDetailsByUserIdAsync(Guid userId); + Task> GetManyDetailsByUserIdAsync(Guid userId); + Task> GetManyDetailsBySubvaultIdAsync(Guid subvaultId); Task> GetPermissionsByUserIdAsync(Guid userId, IEnumerable subvaultIds, Guid organizationId); Task GetCanEditByUserIdCipherIdAsync(Guid userId, Guid cipherId); diff --git a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs index a1ccff874b..16c68f973b 100644 --- a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs +++ b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs @@ -61,7 +61,7 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task>> GetDetailsByIdAsync(Guid id) + public async Task>> GetDetailsByIdAsync(Guid id) { using(var connection = new SqlConnection(ConnectionString)) { @@ -71,8 +71,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/Repositories/SqlServer/SubvaultUserRepository.cs b/src/Core/Repositories/SqlServer/SubvaultUserRepository.cs index 368e16d6d5..60d9306dcc 100644 --- a/src/Core/Repositories/SqlServer/SubvaultUserRepository.cs +++ b/src/Core/Repositories/SqlServer/SubvaultUserRepository.cs @@ -34,12 +34,12 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task> GetManyDetailsByUserIdAsync(Guid userId) + public async Task> GetManyDetailsByUserIdAsync(Guid userId) { using(var connection = new SqlConnection(ConnectionString)) { - var results = await connection.QueryAsync( - $"[{Schema}].[SubvaultUserDetails_ReadByUserId]", + var results = await connection.QueryAsync( + $"[{Schema}].[SubvaultUserSubvaultDetails_ReadByUserId]", new { UserId = userId }, commandType: CommandType.StoredProcedure); @@ -47,6 +47,19 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task> GetManyDetailsBySubvaultIdAsync(Guid subvaultId) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[SubvaultUserUserDetails_ReadBySubvaultId]", + new { SubvaultId = subvaultId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + public async Task> GetPermissionsByUserIdAsync(Guid userId, IEnumerable subvaultIds, Guid organizationId) { diff --git a/src/Core/Services/ISubvaultService.cs b/src/Core/Services/ISubvaultService.cs new file mode 100644 index 0000000000..a1a7c8b8a4 --- /dev/null +++ b/src/Core/Services/ISubvaultService.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using Bit.Core.Models.Business; +using Bit.Core.Models.Table; +using System; +using System.Collections.Generic; + +namespace Bit.Core.Services +{ + public interface ISubvaultService + { + + } +} diff --git a/src/Core/Services/Implementations/SubvaultService.cs b/src/Core/Services/Implementations/SubvaultService.cs new file mode 100644 index 0000000000..89af03cdb0 --- /dev/null +++ b/src/Core/Services/Implementations/SubvaultService.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Bit.Core.Repositories; +using Bit.Core.Models.Business; +using Bit.Core.Models.Table; +using Bit.Core.Utilities; +using Bit.Core.Exceptions; +using System.Collections.Generic; + +namespace Bit.Core.Services +{ + public class SubvaultService : ISubvaultService + { + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly ISubvaultRepository _subvaultRepository; + private readonly ISubvaultUserRepository _subvaultUserRepository; + private readonly IUserRepository _userRepository; + private readonly IMailService _mailService; + + public SubvaultService( + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + ISubvaultRepository subvaultRepository, + ISubvaultUserRepository subvaultUserRepository, + IUserRepository userRepository, + IMailService mailService) + { + _organizationRepository = organizationRepository; + _organizationUserRepository = organizationUserRepository; + _subvaultRepository = subvaultRepository; + _subvaultUserRepository = subvaultUserRepository; + _userRepository = userRepository; + _mailService = mailService; + } + + + } +} diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 55e6ec22f0..acdb437aa9 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -84,7 +84,7 @@ - + @@ -99,7 +99,7 @@ - + @@ -180,5 +180,7 @@ + + \ 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 46f9173c8e..8c11b19678 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadById.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadById.sql @@ -14,7 +14,7 @@ BEGIN SELECT * FROM - [dbo].[SubvaultUserDetailsView] + [dbo].[SubvaultUserSubvaultDetailsView] WHERE [OrganizationUserId] = @Id END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/SubvaultUserDetails_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/SubvaultUserSubvaultDetails_ReadByUserId.sql similarity index 64% rename from src/Sql/dbo/Stored Procedures/SubvaultUserDetails_ReadByUserId.sql rename to src/Sql/dbo/Stored Procedures/SubvaultUserSubvaultDetails_ReadByUserId.sql index b4f6adec56..391897d84e 100644 --- a/src/Sql/dbo/Stored Procedures/SubvaultUserDetails_ReadByUserId.sql +++ b/src/Sql/dbo/Stored Procedures/SubvaultUserSubvaultDetails_ReadByUserId.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [dbo].[SubvaultUserDetails_ReadByUserId] +CREATE PROCEDURE [dbo].[SubvaultUserSubvaultDetails_ReadByUserId] @UserId UNIQUEIDENTIFIER AS BEGIN @@ -7,7 +7,7 @@ BEGIN SELECT SU.* FROM - [dbo].[SubvaultUserDetailsView] SU + [dbo].[SubvaultUserSubvaultDetailsView] SU INNER JOIN [OrganizationUser] OU ON SU.[OrganizationUserId] = OU.[Id] WHERE diff --git a/src/Sql/dbo/Stored Procedures/SubvaultUserUserDetails_ReadBySubvaultId.sql b/src/Sql/dbo/Stored Procedures/SubvaultUserUserDetails_ReadBySubvaultId.sql new file mode 100644 index 0000000000..4e556c3740 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/SubvaultUserUserDetails_ReadBySubvaultId.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[SubvaultUserUserDetails_ReadBySubvaultId] + @SubvaultId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + SU.* + FROM + [dbo].[SubvaultUserUserDetailsView] SU + INNER JOIN + [OrganizationUser] OU ON SU.[OrganizationUserId] = OU.[Id] + WHERE + SU.[SubvaultId] = @SubvaultId +END \ No newline at end of file diff --git a/src/Sql/dbo/Views/SubvaultUserDetailsView.sql b/src/Sql/dbo/Views/SubvaultUserSubvaultDetailsView.sql similarity index 79% rename from src/Sql/dbo/Views/SubvaultUserDetailsView.sql rename to src/Sql/dbo/Views/SubvaultUserSubvaultDetailsView.sql index 0136c7c58e..23e9bcf2be 100644 --- a/src/Sql/dbo/Views/SubvaultUserDetailsView.sql +++ b/src/Sql/dbo/Views/SubvaultUserSubvaultDetailsView.sql @@ -1,4 +1,4 @@ -CREATE VIEW [dbo].[SubvaultUserDetailsView] +CREATE VIEW [dbo].[SubvaultUserSubvaultDetailsView] AS SELECT SU.[Id], diff --git a/src/Sql/dbo/Views/SubvaultUserUserDetailsView.sql b/src/Sql/dbo/Views/SubvaultUserUserDetailsView.sql new file mode 100644 index 0000000000..5ed4c0b38c --- /dev/null +++ b/src/Sql/dbo/Views/SubvaultUserUserDetailsView.sql @@ -0,0 +1,18 @@ +CREATE VIEW [dbo].[SubvaultUserUserDetailsView] +AS +SELECT + SU.[Id], + SU.[OrganizationUserId], + SU.[SubvaultId], + U.[Name], + ISNULL(U.[Email], OU.[Email]) Email, + OU.[Status], + OU.[Type], + SU.[ReadOnly], + SU.[Admin] +FROM + [dbo].[SubvaultUser] SU +INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[Id] = SU.[OrganizationUserId] +LEFT JOIN + [dbo].[User] U ON U.[Id] = OU.[UserId] \ No newline at end of file