mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[PM-16812] Shortcut duplicate group patch requests (#5354)
* Copy PatchGroupCommand to vNext and refactor * Detect duplicate add requests and return early * Update read repository method to use HA replica * Add new write repository method
This commit is contained in:
@ -14,11 +14,29 @@ public interface IGroupRepository : IRepository<Group, Guid>
|
||||
Guid organizationId);
|
||||
Task<ICollection<Group>> GetManyByManyIds(IEnumerable<Guid> groupIds);
|
||||
Task<ICollection<Guid>> GetManyIdsByUserIdAsync(Guid organizationUserId);
|
||||
Task<ICollection<Guid>> GetManyUserIdsByIdAsync(Guid id);
|
||||
/// <summary>
|
||||
/// Query all OrganizationUserIds who are a member of the specified group.
|
||||
/// </summary>
|
||||
/// <param name="id">The group id.</param>
|
||||
/// <param name="useReadOnlyReplica">
|
||||
/// Whether to use the high-availability database replica. This is for paths with high traffic where immediate data
|
||||
/// consistency is not required. You generally do not want this.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
Task<ICollection<Guid>> GetManyUserIdsByIdAsync(Guid id, bool useReadOnlyReplica = false);
|
||||
Task<ICollection<GroupUser>> GetManyGroupUsersByOrganizationIdAsync(Guid organizationId);
|
||||
Task CreateAsync(Group obj, IEnumerable<CollectionAccessSelection> collections);
|
||||
Task ReplaceAsync(Group obj, IEnumerable<CollectionAccessSelection> collections);
|
||||
Task DeleteUserAsync(Guid groupId, Guid organizationUserId);
|
||||
/// <summary>
|
||||
/// Update a group's members. Replaces all members currently in the group.
|
||||
/// Ignores members that do not belong to the same organization as the group.
|
||||
/// </summary>
|
||||
Task UpdateUsersAsync(Guid groupId, IEnumerable<Guid> organizationUserIds);
|
||||
/// <summary>
|
||||
/// Add members to a group. Gracefully ignores members that are already in the group,
|
||||
/// duplicate organizationUserIds, and organizationUsers who are not part of the organization.
|
||||
/// </summary>
|
||||
Task AddGroupUsersByIdAsync(Guid groupId, IEnumerable<Guid> organizationUserIds);
|
||||
Task DeleteManyAsync(IEnumerable<Guid> groupIds);
|
||||
}
|
||||
|
@ -108,6 +108,7 @@ public static class FeatureFlagKeys
|
||||
public const string IntegrationPage = "pm-14505-admin-console-integration-page";
|
||||
public const string DeviceApprovalRequestAdminNotifications = "pm-15637-device-approval-request-admin-notifications";
|
||||
public const string LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission";
|
||||
public const string ShortcutDuplicatePatchRequests = "pm-16812-shortcut-duplicate-patch-requests";
|
||||
public const string PushSyncOrgKeysOnRevokeRestore = "pm-17168-push-sync-org-keys-on-revoke-restore";
|
||||
|
||||
/* Tools Team */
|
||||
|
@ -109,9 +109,13 @@ public class GroupRepository : Repository<Group, Guid>, IGroupRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<Guid>> GetManyUserIdsByIdAsync(Guid id)
|
||||
public async Task<ICollection<Guid>> GetManyUserIdsByIdAsync(Guid id, bool useReadOnlyReplica = false)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
var connectionString = useReadOnlyReplica
|
||||
? ReadOnlyConnectionString
|
||||
: ConnectionString;
|
||||
|
||||
using (var connection = new SqlConnection(connectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<Guid>(
|
||||
$"[{Schema}].[GroupUser_ReadOrganizationUserIdsByGroupId]",
|
||||
@ -186,6 +190,17 @@ public class GroupRepository : Repository<Group, Guid>, IGroupRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddGroupUsersByIdAsync(Guid groupId, IEnumerable<Guid> organizationUserIds)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
"[dbo].[GroupUser_AddUsers]",
|
||||
new { GroupId = groupId, OrganizationUserIds = organizationUserIds.ToGuidIdArrayTVP() },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteManyAsync(IEnumerable<Guid> groupIds)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
|
@ -163,8 +163,10 @@ public class GroupRepository : Repository<AdminConsoleEntities.Group, Group, Gui
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<Guid>> GetManyUserIdsByIdAsync(Guid id)
|
||||
public async Task<ICollection<Guid>> GetManyUserIdsByIdAsync(Guid id, bool useReadOnlyReplica = false)
|
||||
{
|
||||
// EF is only used for self-hosted so read-only replica parameter is ignored
|
||||
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
@ -255,6 +257,29 @@ public class GroupRepository : Repository<AdminConsoleEntities.Group, Group, Gui
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddGroupUsersByIdAsync(Guid groupId, IEnumerable<Guid> organizationUserIds)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var orgId = (await dbContext.Groups.FindAsync(groupId)).OrganizationId;
|
||||
var insert = from ou in dbContext.OrganizationUsers
|
||||
where organizationUserIds.Contains(ou.Id) &&
|
||||
ou.OrganizationId == orgId &&
|
||||
!dbContext.GroupUsers.Any(gu => gu.GroupId == groupId && ou.Id == gu.OrganizationUserId)
|
||||
select new GroupUser
|
||||
{
|
||||
GroupId = groupId,
|
||||
OrganizationUserId = ou.Id,
|
||||
};
|
||||
await dbContext.AddRangeAsync(insert);
|
||||
|
||||
await dbContext.SaveChangesAsync();
|
||||
await dbContext.UserBumpAccountRevisionDateByOrganizationIdAsync(orgId);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteManyAsync(IEnumerable<Guid> groupIds)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
|
39
src/Sql/dbo/Stored Procedures/GroupUser_AddUsers.sql
Normal file
39
src/Sql/dbo/Stored Procedures/GroupUser_AddUsers.sql
Normal file
@ -0,0 +1,39 @@
|
||||
CREATE PROCEDURE [dbo].[GroupUser_AddUsers]
|
||||
@GroupId UNIQUEIDENTIFIER,
|
||||
@OrganizationUserIds AS [dbo].[GuidIdArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DECLARE @OrgId UNIQUEIDENTIFIER = (
|
||||
SELECT TOP 1
|
||||
[OrganizationId]
|
||||
FROM
|
||||
[dbo].[Group]
|
||||
WHERE
|
||||
[Id] = @GroupId
|
||||
)
|
||||
|
||||
-- Insert
|
||||
INSERT INTO
|
||||
[dbo].[GroupUser] (GroupId, OrganizationUserId)
|
||||
SELECT DISTINCT
|
||||
@GroupId,
|
||||
[Source].[Id]
|
||||
FROM
|
||||
@OrganizationUserIds AS [Source]
|
||||
INNER JOIN
|
||||
[dbo].[OrganizationUser] OU ON [Source].[Id] = OU.[Id] AND OU.[OrganizationId] = @OrgId
|
||||
WHERE
|
||||
NOT EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
[dbo].[GroupUser]
|
||||
WHERE
|
||||
[GroupId] = @GroupId
|
||||
AND [OrganizationUserId] = [Source].[Id]
|
||||
)
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId
|
||||
END
|
Reference in New Issue
Block a user