From 2b8db4d1edb6da9c9b85b060469e652e7193a769 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin <kyle.spearrin@gmail.com> Date: Thu, 11 May 2017 11:41:13 -0400 Subject: [PATCH] SelectionReadOnly MERGE to CollectionGroup --- src/Api/Controllers/GroupsController.cs | 4 +-- .../Models/Api/Request/GroupRequestModel.cs | 2 +- .../OrganizationUserRequestModels.cs | 23 ++----------- .../Request/SelectionReadOnlyRequestModel.cs | 33 +++++++++++++++++++ .../Models/Api/Response/GroupResponseModel.cs | 8 +++-- .../Response/OrganizationUserResponseModel.cs | 21 ++---------- .../SelectionReadOnlyResponseModel.cs | 22 +++++++++++++ src/Core/Models/Table/CollectionGroup.cs | 2 +- src/Core/Repositories/IGroupRepository.cs | 6 ++-- .../Repositories/SqlServer/GroupRepository.cs | 16 ++++----- src/Core/Services/IGroupService.cs | 4 +-- .../Services/Implementations/GroupService.cs | 11 ++++--- src/Core/Utilities/CoreHelpers.cs | 28 +++++++++++++++- src/Sql/Sql.sqlproj | 1 + .../Group_CreateWithCollections.sql | 6 ++-- .../Group_ReadWithCollectionsById.sql | 3 +- .../Group_UpdateWithCollections.sql | 8 +++-- .../SelectionReadOnlyArray.sql | 4 +++ 18 files changed, 129 insertions(+), 73 deletions(-) create mode 100644 src/Core/Models/Api/Request/SelectionReadOnlyRequestModel.cs create mode 100644 src/Core/Models/Api/Response/SelectionReadOnlyResponseModel.cs create mode 100644 src/Sql/dbo/User Defined Types/SelectionReadOnlyArray.sql diff --git a/src/Api/Controllers/GroupsController.cs b/src/Api/Controllers/GroupsController.cs index 8a8feffca6..e9eea63a51 100644 --- a/src/Api/Controllers/GroupsController.cs +++ b/src/Api/Controllers/GroupsController.cs @@ -93,7 +93,7 @@ namespace Bit.Api.Controllers } var group = model.ToGroup(orgIdGuid); - await _groupService.SaveAsync(group, model.CollectionIds?.Select(c => new Guid(c))); + await _groupService.SaveAsync(group, model.Collections?.Select(c => c.ToSelectionReadOnly())); return new GroupResponseModel(group); } @@ -107,7 +107,7 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - await _groupService.SaveAsync(model.ToGroup(group), model.CollectionIds?.Select(c => new Guid(c))); + await _groupService.SaveAsync(model.ToGroup(group), model.Collections?.Select(c => c.ToSelectionReadOnly())); return new GroupResponseModel(group); } diff --git a/src/Core/Models/Api/Request/GroupRequestModel.cs b/src/Core/Models/Api/Request/GroupRequestModel.cs index 6125e32b20..d5366f8d3e 100644 --- a/src/Core/Models/Api/Request/GroupRequestModel.cs +++ b/src/Core/Models/Api/Request/GroupRequestModel.cs @@ -13,7 +13,7 @@ namespace Bit.Core.Models.Api public string Name { get; set; } [Required] public bool? AccessAll { get; set; } - public IEnumerable<string> CollectionIds { get; set; } + public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; } public Group ToGroup(Guid orgId) { diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationUserRequestModels.cs b/src/Core/Models/Api/Request/Organizations/OrganizationUserRequestModels.cs index 6fa68d38f9..e694a4fcce 100644 --- a/src/Core/Models/Api/Request/Organizations/OrganizationUserRequestModels.cs +++ b/src/Core/Models/Api/Request/Organizations/OrganizationUserRequestModels.cs @@ -1,5 +1,4 @@ using Bit.Core.Models.Table; -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -13,7 +12,7 @@ namespace Bit.Core.Models.Api [Required] public Enums.OrganizationUserType? Type { get; set; } public bool AccessAll { get; set; } - public IEnumerable<OrganizationUserCollectionRequestModel> Collections { get; set; } + public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; } } public class OrganizationUserAcceptRequestModel @@ -33,7 +32,7 @@ namespace Bit.Core.Models.Api [Required] public Enums.OrganizationUserType? Type { get; set; } public bool AccessAll { get; set; } - public IEnumerable<OrganizationUserCollectionRequestModel> Collections { get; set; } + public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; } public OrganizationUser ToOrganizationUser(OrganizationUser existingUser) { @@ -48,22 +47,4 @@ namespace Bit.Core.Models.Api [Required] public IEnumerable<string> GroupIds { get; set; } } - - public class OrganizationUserCollectionRequestModel - { - [Required] - public string CollectionId { get; set; } - public bool ReadOnly { get; set; } - - public CollectionUser ToCollectionUser() - { - var collection = new CollectionUser - { - ReadOnly = ReadOnly, - CollectionId = new Guid(CollectionId) - }; - - return collection; - } - } } diff --git a/src/Core/Models/Api/Request/SelectionReadOnlyRequestModel.cs b/src/Core/Models/Api/Request/SelectionReadOnlyRequestModel.cs new file mode 100644 index 0000000000..9833946e60 --- /dev/null +++ b/src/Core/Models/Api/Request/SelectionReadOnlyRequestModel.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using Bit.Core.Models.Table; +using Bit.Core.Models.Data; + +namespace Bit.Core.Models.Api +{ + public class SelectionReadOnlyRequestModel + { + [Required] + public string Id { get; set; } + public bool ReadOnly { get; set; } + + public CollectionUser ToCollectionUser() + { + return new CollectionUser + { + ReadOnly = ReadOnly, + CollectionId = new Guid(Id) + }; + } + + public SelectionReadOnly ToSelectionReadOnly() + { + return new SelectionReadOnly + { + Id = new Guid(Id), + ReadOnly = ReadOnly + }; + } + } +} diff --git a/src/Core/Models/Api/Response/GroupResponseModel.cs b/src/Core/Models/Api/Response/GroupResponseModel.cs index 1b4cabd057..39d4dc8538 100644 --- a/src/Core/Models/Api/Response/GroupResponseModel.cs +++ b/src/Core/Models/Api/Response/GroupResponseModel.cs @@ -1,6 +1,8 @@ using System; using Bit.Core.Models.Table; using System.Collections.Generic; +using Bit.Core.Models.Data; +using System.Linq; namespace Bit.Core.Models.Api { @@ -28,12 +30,12 @@ namespace Bit.Core.Models.Api public class GroupDetailsResponseModel : GroupResponseModel { - public GroupDetailsResponseModel(Group group, IEnumerable<Guid> collectionIds) + public GroupDetailsResponseModel(Group group, IEnumerable<SelectionReadOnly> collections) : base(group, "groupDetails") { - CollectionIds = collectionIds; + Collections = collections.Select(c => new SelectionReadOnlyResponseModel(c)); } - public IEnumerable<Guid> CollectionIds { get; set; } + public IEnumerable<SelectionReadOnlyResponseModel> Collections { get; set; } } } diff --git a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs index 0b1ef2612e..43c80fcfa6 100644 --- a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs @@ -52,27 +52,10 @@ namespace Bit.Core.Models.Api IEnumerable<SelectionReadOnly> collections) : base(organizationUser, "organizationUserDetails") { - Collections = collections.Select(c => new CollectionSelection(c)); + Collections = collections.Select(c => new SelectionReadOnlyResponseModel(c)); } - public IEnumerable<CollectionSelection> Collections { get; set; } - - public class CollectionSelection - { - public CollectionSelection(Data.SelectionReadOnly selection) - { - if(selection == null) - { - throw new ArgumentNullException(nameof(selection)); - } - - Id = selection.Id.ToString(); - ReadOnly = selection.ReadOnly; - } - - public string Id { get; set; } - public bool ReadOnly { get; set; } - } + public IEnumerable<SelectionReadOnlyResponseModel> Collections { get; set; } } public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponseModel { diff --git a/src/Core/Models/Api/Response/SelectionReadOnlyResponseModel.cs b/src/Core/Models/Api/Response/SelectionReadOnlyResponseModel.cs new file mode 100644 index 0000000000..dce5ffd456 --- /dev/null +++ b/src/Core/Models/Api/Response/SelectionReadOnlyResponseModel.cs @@ -0,0 +1,22 @@ +using System; +using Bit.Core.Models.Data; + +namespace Bit.Core.Models.Api +{ + public class SelectionReadOnlyResponseModel + { + public SelectionReadOnlyResponseModel(SelectionReadOnly selection) + { + if(selection == null) + { + throw new ArgumentNullException(nameof(selection)); + } + + Id = selection.Id.ToString(); + ReadOnly = selection.ReadOnly; + } + + public string Id { get; set; } + public bool ReadOnly { get; set; } + } +} diff --git a/src/Core/Models/Table/CollectionGroup.cs b/src/Core/Models/Table/CollectionGroup.cs index 5e6d516a56..d666d73443 100644 --- a/src/Core/Models/Table/CollectionGroup.cs +++ b/src/Core/Models/Table/CollectionGroup.cs @@ -5,7 +5,7 @@ namespace Bit.Core.Models.Table public class CollectionGroup { public Guid CollectionId { get; set; } - public Guid OrganizationUserId { get; set; } + public Guid GroupId { get; set; } public bool ReadOnly { get; set; } } } diff --git a/src/Core/Repositories/IGroupRepository.cs b/src/Core/Repositories/IGroupRepository.cs index 53627cb3f3..84b235d34e 100644 --- a/src/Core/Repositories/IGroupRepository.cs +++ b/src/Core/Repositories/IGroupRepository.cs @@ -8,12 +8,12 @@ namespace Bit.Core.Repositories { public interface IGroupRepository : IRepository<Group, Guid> { - Task<Tuple<Group, ICollection<Guid>>> GetByIdWithCollectionsAsync(Guid id); + Task<Tuple<Group, ICollection<SelectionReadOnly>>> GetByIdWithCollectionsAsync(Guid id); Task<ICollection<Group>> GetManyByOrganizationIdAsync(Guid organizationId); Task<ICollection<GroupUserUserDetails>> GetManyUserDetailsByIdAsync(Guid id); Task<ICollection<Guid>> GetManyIdsByUserIdAsync(Guid organizationUserId); - Task CreateAsync(Group obj, IEnumerable<Guid> collectionIds); - Task ReplaceAsync(Group obj, IEnumerable<Guid> collectionIds); + Task CreateAsync(Group obj, IEnumerable<SelectionReadOnly> collections); + Task ReplaceAsync(Group obj, IEnumerable<SelectionReadOnly> collections); Task DeleteUserAsync(Guid groupId, Guid organizationUserId); } } diff --git a/src/Core/Repositories/SqlServer/GroupRepository.cs b/src/Core/Repositories/SqlServer/GroupRepository.cs index f2013c02c1..2ec55f75fc 100644 --- a/src/Core/Repositories/SqlServer/GroupRepository.cs +++ b/src/Core/Repositories/SqlServer/GroupRepository.cs @@ -22,7 +22,7 @@ namespace Bit.Core.Repositories.SqlServer : base(connectionString) { } - public async Task<Tuple<Group, ICollection<Guid>>> GetByIdWithCollectionsAsync(Guid id) + public async Task<Tuple<Group, ICollection<SelectionReadOnly>>> GetByIdWithCollectionsAsync(Guid id) { using(var connection = new SqlConnection(ConnectionString)) { @@ -32,9 +32,9 @@ namespace Bit.Core.Repositories.SqlServer commandType: CommandType.StoredProcedure); var group = await results.ReadFirstOrDefaultAsync<Group>(); - var colletionIds = (await results.ReadAsync<Guid>()).ToList(); + var colletions = (await results.ReadAsync<SelectionReadOnly>()).ToList(); - return new Tuple<Group, ICollection<Guid>>(group, colletionIds); + return new Tuple<Group, ICollection<SelectionReadOnly>>(group, colletions); } } @@ -77,11 +77,11 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task CreateAsync(Group obj, IEnumerable<Guid> collectionIds) + public async Task CreateAsync(Group obj, IEnumerable<SelectionReadOnly> collections) { obj.SetNewId(); var objWithCollections = JsonConvert.DeserializeObject<GroupWithCollections>(JsonConvert.SerializeObject(obj)); - objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); + objWithCollections.Collections = collections.ToArrayTVP(); using(var connection = new SqlConnection(ConnectionString)) { @@ -92,10 +92,10 @@ namespace Bit.Core.Repositories.SqlServer } } - public async Task ReplaceAsync(Group obj, IEnumerable<Guid> collectionIds) + public async Task ReplaceAsync(Group obj, IEnumerable<SelectionReadOnly> collections) { var objWithCollections = JsonConvert.DeserializeObject<GroupWithCollections>(JsonConvert.SerializeObject(obj)); - objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); + objWithCollections.Collections = collections.ToArrayTVP(); using(var connection = new SqlConnection(ConnectionString)) { @@ -119,7 +119,7 @@ namespace Bit.Core.Repositories.SqlServer public class GroupWithCollections : Group { - public DataTable CollectionIds { get; set; } + public DataTable Collections { get; set; } } } } diff --git a/src/Core/Services/IGroupService.cs b/src/Core/Services/IGroupService.cs index 303fc36849..602ebc44f5 100644 --- a/src/Core/Services/IGroupService.cs +++ b/src/Core/Services/IGroupService.cs @@ -1,12 +1,12 @@ using System.Threading.Tasks; using Bit.Core.Models.Table; using System.Collections.Generic; -using System; +using Bit.Core.Models.Data; namespace Bit.Core.Services { public interface IGroupService { - Task SaveAsync(Group group, IEnumerable<Guid> collectionIds = null); + Task SaveAsync(Group group, IEnumerable<SelectionReadOnly> collections = null); } } diff --git a/src/Core/Services/Implementations/GroupService.cs b/src/Core/Services/Implementations/GroupService.cs index 9e8bc0ba21..2728d88abe 100644 --- a/src/Core/Services/Implementations/GroupService.cs +++ b/src/Core/Services/Implementations/GroupService.cs @@ -4,6 +4,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Table; using Bit.Core.Repositories; using System.Collections.Generic; +using Bit.Core.Models.Data; namespace Bit.Core.Services { @@ -20,7 +21,7 @@ namespace Bit.Core.Services _groupRepository = groupRepository; } - public async Task SaveAsync(Group group, IEnumerable<Guid> collectionIds = null) + public async Task SaveAsync(Group group, IEnumerable<SelectionReadOnly> collections = null) { var org = await _organizationRepository.GetByIdAsync(group.OrganizationId); if(org == null) @@ -35,24 +36,24 @@ namespace Bit.Core.Services if(group.Id == default(Guid)) { - if(collectionIds == null) + if(collections == null) { await _groupRepository.CreateAsync(group); } else { - await _groupRepository.CreateAsync(group, collectionIds); + await _groupRepository.CreateAsync(group, collections); } } else { - if(collectionIds == null) + if(collections == null) { await _groupRepository.ReplaceAsync(group); } else { - await _groupRepository.ReplaceAsync(group, collectionIds); + await _groupRepository.ReplaceAsync(group, collections); } } } diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index 46c3bafe03..85b482b846 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -1,4 +1,6 @@ -using Dapper; +using Bit.Core.Models.Data; +using Bit.Core.Models.Table; +using Dapper; using System; using System.Collections.Generic; using System.Data; @@ -65,6 +67,30 @@ namespace Bit.Core.Utilities return table; } + public static DataTable ToArrayTVP(this IEnumerable<SelectionReadOnly> values) + { + var table = new DataTable(); + table.SetTypeName("[dbo].[SelectionReadOnlyArray]"); + + var idColumn = new DataColumn("Id", typeof(Guid)); + table.Columns.Add(idColumn); + var readOnlyColumn = new DataColumn("ReadOnly", typeof(bool)); + table.Columns.Add(readOnlyColumn); + + if(values != null) + { + foreach(var value in values) + { + var row = table.NewRow(); + row[idColumn] = value.Id; + row[readOnlyColumn] = value.ReadOnly; + table.Rows.Add(row); + } + } + + return table; + } + public static X509Certificate2 GetCertificate(string thumbprint) { // Clean possible garbage characters from thumbprint copy/paste diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 011a8da6c3..2c70828afa 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -193,5 +193,6 @@ <Build Include="dbo\Stored Procedures\GroupUser_ReadGroupIdsByOrganizationUserId.sql" /> <Build Include="dbo\Stored Procedures\GroupUser_UpdateGroups.sql" /> <Build Include="dbo\Stored Procedures\GroupUser_Delete.sql" /> + <Build Include="dbo\User Defined Types\SelectionReadOnlyArray.sql" /> </ItemGroup> </Project> \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql index 523455604b..be3787c75f 100644 --- a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql @@ -5,7 +5,7 @@ @AccessAll BIT, @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), - @CollectionIds AS [dbo].[GuidIdArray] READONLY + @Collections AS [dbo].[SelectionReadOnlyArray] READONLY AS BEGIN SET NOCOUNT ON @@ -29,9 +29,9 @@ BEGIN SELECT [Id], @Id, - 0 + [ReadOnly] FROM - @CollectionIds + @Collections WHERE [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Group_ReadWithCollectionsById.sql b/src/Sql/dbo/Stored Procedures/Group_ReadWithCollectionsById.sql index fb9d2951e4..e06a8cb9aa 100644 --- a/src/Sql/dbo/Stored Procedures/Group_ReadWithCollectionsById.sql +++ b/src/Sql/dbo/Stored Procedures/Group_ReadWithCollectionsById.sql @@ -7,7 +7,8 @@ BEGIN EXEC [dbo].[Group_ReadById] @Id SELECT - [CollectionId] + [CollectionId] [Id], + [ReadOnly] FROM [dbo].[CollectionGroup] WHERE diff --git a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql index ce16903fa7..549ae193ca 100644 --- a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql @@ -5,7 +5,7 @@ @AccessAll BIT, @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), - @CollectionIds AS [dbo].[GuidIdArray] READONLY + @Collections AS [dbo].[SelectionReadOnlyArray] READONLY AS BEGIN SET NOCOUNT ON @@ -23,7 +23,7 @@ BEGIN MERGE [dbo].[CollectionGroup] AS [Target] USING - @CollectionIds AS [Source] + @Collections AS [Source] ON [Target].[CollectionId] = [Source].[Id] AND [Target].[GroupId] = @Id @@ -33,8 +33,10 @@ BEGIN ( [Source].[Id], @Id, - 0 + [Source].[ReadOnly] ) + WHEN MATCHED AND [Target].[ReadOnly] != [Source].[ReadOnly] THEN + UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly] WHEN NOT MATCHED BY SOURCE AND [Target].[GroupId] = @Id THEN DELETE diff --git a/src/Sql/dbo/User Defined Types/SelectionReadOnlyArray.sql b/src/Sql/dbo/User Defined Types/SelectionReadOnlyArray.sql new file mode 100644 index 0000000000..456d913e01 --- /dev/null +++ b/src/Sql/dbo/User Defined Types/SelectionReadOnlyArray.sql @@ -0,0 +1,4 @@ +CREATE TYPE [dbo].[SelectionReadOnlyArray] AS TABLE ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [ReadOnly] BIT NOT NULL); +