Skip to content

Commit

Permalink
Add new repository method
Browse files Browse the repository at this point in the history
  • Loading branch information
eliykat committed Jan 31, 2025
1 parent 42c58ca commit 450bc40
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 7 deletions.
13 changes: 6 additions & 7 deletions bitwarden_license/src/Scim/Groups/PatchGroupCommandvNext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private async Task HandleOperationAsync(Group group, ScimPatchModel.OperationMod
case PatchOps.Add when
operation.Path?.ToLowerInvariant() == PatchPaths.Members:
{
await AddMembersAsync(group, GetOperationValueIds(operation.Value).ToHashSet());
await AddMembersAsync(group, GetOperationValueIds(operation.Value));
break;
}

Expand Down Expand Up @@ -122,20 +122,19 @@ private async Task HandleOperationAsync(Group group, ScimPatchModel.OperationMod
}
}

private async Task AddMembersAsync(Group group, HashSet<Guid> usersToAdd)
private async Task AddMembersAsync(Group group, List<Guid> usersToAdd)
{
// Azure Entra ID is known to send redundant "add" requests for each existing member every time any member
// is removed. To avoid excessive load on the database we detect these and return early.
// is removed. To avoid excessive load on the database, we check against the high availability replica and
// return early if they already exist.
var groupMembers = await _groupRepository.GetManyUserIdsByIdAsync(group.Id, useReadOnlyReplica: true);
if (usersToAdd.IsSubsetOf(groupMembers))
if (usersToAdd.ToHashSet().IsSubsetOf(groupMembers))
{
_logger.LogDebug("Ignoring duplicate SCIM request to add members {Members} to group {Group}", usersToAdd, group.Id);
return;
}

// TODO: don't use the results of the previous query, just write a new upsert operation
var updatedMembers = groupMembers.Concat(usersToAdd).ToHashSet();
await _groupRepository.UpdateUsersAsync(group.Id, updatedMembers);
await _groupRepository.AddGroupUsersByIdAsync(group.Id, usersToAdd);
}

private List<Guid> GetOperationValueIds(JsonElement objArray)
Expand Down
7 changes: 7 additions & 0 deletions src/Core/AdminConsole/Repositories/IGroupRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ Task<ICollection<Tuple<Group, ICollection<CollectionAccessSelection>>>> GetManyW
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.
/// </summary>
Task UpdateUsersAsync(Guid groupId, IEnumerable<Guid> organizationUserIds);
/// <summary>
/// Add members to a group. Ignores members that are already in the group.
/// </summary>
Task AddGroupUsersByIdAsync(Guid groupId, IEnumerable<Guid> organizationUserIds);
Task DeleteManyAsync(IEnumerable<Guid> groupIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ public async Task UpdateUsersAsync(Guid groupId, IEnumerable<Guid> organizationU
}
}

public async Task AddGroupUsersByIdAsync(Guid groupId, IEnumerable<Guid> organizationUserIds)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
"[dbo].[GroupUser_UpsertUsers]",
new { GroupId = groupId, OrganizationUserIds = organizationUserIds.ToGuidIdArrayTVP() },
commandType: CommandType.StoredProcedure);
}
}

public async Task DeleteManyAsync(IEnumerable<Guid> groupIds)
{
using (var connection = new SqlConnection(ConnectionString))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,29 @@ where organizationUserIds.Contains(ou.Id) &&
}
}

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())
Expand Down
39 changes: 39 additions & 0 deletions src/Sql/dbo/Stored Procedures/GroupUser_AddUsers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
CREATE PROCEDURE [dbo].[GroupUser_UpdateUsers]
@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]
SELECT
@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
39 changes: 39 additions & 0 deletions util/Migrator/DbScripts/2025-02-03_00_GroupUser_AddUsers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
CREATE OR ALTER PROCEDURE [dbo].[GroupUser_UpdateUsers]
@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]
SELECT
@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

0 comments on commit 450bc40

Please sign in to comment.