diff --git a/src/Core/Models/Table/GroupUser.cs b/src/Core/Models/Table/GroupUser.cs new file mode 100644 index 0000000000..524fedd366 --- /dev/null +++ b/src/Core/Models/Table/GroupUser.cs @@ -0,0 +1,11 @@ +using System; +using Bit.Core.Utilities; + +namespace Bit.Core.Models.Table +{ + public class GroupUser + { + public Guid GroupId { get; set; } + public Guid OrganizationUserId { get; set; } + } +} diff --git a/src/Core/Repositories/IGroupRepository.cs b/src/Core/Repositories/IGroupRepository.cs index 56065b7cf9..837c4522d0 100644 --- a/src/Core/Repositories/IGroupRepository.cs +++ b/src/Core/Repositories/IGroupRepository.cs @@ -12,6 +12,7 @@ namespace Bit.Core.Repositories Task> GetManyByOrganizationIdAsync(Guid organizationId); Task> GetManyUserDetailsByIdAsync(Guid id); Task> GetManyIdsByUserIdAsync(Guid organizationUserId); + Task> GetManyGroupUsersByOrganizationIdAsync(Guid organizationId); Task CreateAsync(Group obj, IEnumerable collections); Task ReplaceAsync(Group obj, IEnumerable collections); Task DeleteUserAsync(Guid groupId, Guid organizationUserId); diff --git a/src/Core/Repositories/SqlServer/GroupRepository.cs b/src/Core/Repositories/SqlServer/GroupRepository.cs index 4ef8e87f2f..73e65871db 100644 --- a/src/Core/Repositories/SqlServer/GroupRepository.cs +++ b/src/Core/Repositories/SqlServer/GroupRepository.cs @@ -77,6 +77,19 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task> GetManyGroupUsersByOrganizationIdAsync(Guid organizationId) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[GroupUser_ReadByOrganizationId]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + public async Task CreateAsync(Group obj, IEnumerable collections) { obj.SetNewId(); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index ac00441964..6425c48c67 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -916,27 +916,35 @@ namespace Bit.Core.Services var existingGroups = (await _groupRepository.GetManyByOrganizationIdAsync(organizationId)).ToList(); var existingGroupsDict = existingGroups.ToDictionary(g => g.ExternalId); - var newGroups = groups.Where(g => !existingGroupsDict.ContainsKey(g.ExternalId)); - var updateGroups = existingGroups.Where(eg => groups.Any(g => g.ExternalId == eg.ExternalId && g.Name != eg.Name)); - - foreach(var group in newGroups) + if(groups?.Any() ?? false) { - group.CreationDate = group.RevisionDate = DateTime.UtcNow; - await _groupRepository.CreateAsync(group); - } + var newGroups = groups.Where(g => !existingGroupsDict.ContainsKey(g.ExternalId)); + var updateGroups = existingGroups.Where(eg => groups.Any(g => g.ExternalId == eg.ExternalId && g.Name != eg.Name)); - foreach(var group in updateGroups) - { - group.RevisionDate = DateTime.UtcNow; - group.Name = existingGroupsDict[group.ExternalId].Name; - await _groupRepository.ReplaceAsync(group); - } + foreach(var group in newGroups) + { + group.CreationDate = group.RevisionDate = DateTime.UtcNow; + await _groupRepository.CreateAsync(group); + } - // Add the newly created groups to existing groups so that we have a complete list to reference below for users. - existingGroups.AddRange(newGroups); - existingGroupsDict = existingGroups.ToDictionary(g => g.ExternalId); + foreach(var group in updateGroups) + { + group.RevisionDate = DateTime.UtcNow; + group.Name = existingGroupsDict[group.ExternalId].Name; + await _groupRepository.ReplaceAsync(group); + } + + // Add the newly created groups to existing groups so that we have a complete list to reference below for users. + existingGroups.AddRange(newGroups); + existingGroupsDict = existingGroups.ToDictionary(g => g.ExternalId); + } // Users + if(users?.Any() ?? false) + { + return; + } + var existingUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId); var existingUsersDict = existingUsers.ToDictionary(u => u.Email); @@ -975,6 +983,7 @@ namespace Bit.Core.Services } } + var existingGroupUsers = await _groupRepository.GetManyGroupUsersByOrganizationIdAsync(organizationId); foreach(var user in updateUsers) { if(!existingUsersDict.ContainsKey(user.Key)) @@ -983,11 +992,16 @@ namespace Bit.Core.Services } var existingUser = existingUsersDict[user.Key]; - var groupsIdsForUser = user.Value.Where(id => existingGroupsDict.ContainsKey(id)) - .Select(id => existingGroupsDict[id].Id).ToList(); - if(groupsIdsForUser.Any()) + var existingGroupIdsForUser = new HashSet(existingGroupUsers + .Where(gu => gu.OrganizationUserId == existingUser.Id) + .Select(gu => gu.GroupId)); + var newGroupsIdsForUser = new HashSet(user.Value + .Where(id => existingGroupsDict.ContainsKey(id)) + .Select(id => existingGroupsDict[id].Id)); + + if(!existingGroupIdsForUser.SetEquals(newGroupsIdsForUser)) { - await _organizationUserRepository.UpdateGroupsAsync(existingUser.Id, groupsIdsForUser); + await _organizationUserRepository.UpdateGroupsAsync(existingUser.Id, newGroupsIdsForUser); } } } diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 5f33d0eb0d..becbb1c1d5 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -189,5 +189,6 @@ + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/GroupUser_ReadByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/GroupUser_ReadByOrganizationId.sql new file mode 100644 index 0000000000..87d2632134 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/GroupUser_ReadByOrganizationId.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[GroupUser_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + GU.* + FROM + [dbo].[GroupUser] GU + INNER JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + WHERE + G.[OrganizationId] = @OrganizationId +END \ No newline at end of file