Skip to content

[PM-41270] feat: add CollectionGroup authorization and access command - #8132

Draft
r-tome wants to merge 4 commits into
ac/pm-12473/collection-user-access-endpointfrom
ac/pm-41270/collection-group-auth-handler
Draft

[PM-41270] feat: add CollectionGroup authorization and access command#8132
r-tome wants to merge 4 commits into
ac/pm-12473/collection-user-access-endpointfrom
ac/pm-41270/collection-group-auth-handler

Conversation

@r-tome

@r-tome r-tome commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-41270

📔 Objective

Adds the CollectionGroup groundwork that mirrors PM-12473 for groups. No endpoint is added here — the unified collection PATCH endpoint lands in PM-41448.

Includes CollectionGroup_DeleteMany stored procedure, ModifyGroupAccessAsync on ICollectionRepository (Dapper + EF Core), CollectionGroupAuthorizationHandler / CollectionGroupAuthorizationRules with supporting resource and operations types, ModifyCollectionGroupAccessCommand and ModifyCollectionGroupAccessValidator, and DI registrations.

📸 Screenshots

N/A — server-only change.

r-tome added 4 commits August 4, 2026 14:18
…across multiple collections

Adds ModifyGroupAccessAsync to ICollectionRepository with Dapper (SQL
Server) and EF Core (Postgres/MySQL/SQLite) implementations. A new
CollectionGroup_DeleteMany sproc ensures removes and upserts are applied
atomically in one transaction.
…p access

Adds CollectionGroupAuthorizationHandler, CollectionGroupAuthorizationRules,
CollectionGroupOperations, and CollectionGroupAccessResource — a direct
mirror of the CollectionUser authorization layer. No self-add check
since groups don't have that concept.
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.66667% with 121 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.92%. Comparing base (9260d96) to head (6e27e01).

Files with missing lines Patch % Lines
.../AdminConsole/Repositories/CollectionRepository.cs 0.00% 77 Missing ⚠️
.../AdminConsole/Repositories/CollectionRepository.cs 0.00% 39 Missing ⚠️
...Collections/CollectionGroupAuthorizationHandler.cs 92.00% 2 Missing and 2 partials ⚠️
...roupAccess/ModifyCollectionGroupAccessValidator.cs 98.41% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@                               Coverage Diff                               @@
##           ac/pm-12473/collection-user-access-endpoint    #8132      +/-   ##
===============================================================================
- Coverage                                        67.44%   66.92%   -0.52%     
===============================================================================
  Files                                             2317     2309       -8     
  Lines                                           100516   100539      +23     
  Branches                                          9050     9087      +37     
===============================================================================
- Hits                                             67790    67284     -506     
- Misses                                           30453    30976     +523     
- Partials                                          2273     2279       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: REQUEST CHANGES

Reviewed the CollectionGroup groundwork: the new CollectionGroup_DeleteMany stored procedure and matching migration, ModifyGroupAccessAsync on both the Dapper and EF Core CollectionRepository, the CollectionGroupAuthorizationHandler / CollectionGroupAuthorizationRules pair, and the ModifyCollectionGroupAccessCommand / validator with DI registrations. The change closely mirrors the PM-12473 user-access work, and the validator, command, and Dapper implementations line up with their ModifyUserAccess counterparts. Three findings relate to places where the mirroring diverged from the group-specific semantics that already exist elsewhere in the codebase. Note the base branch is ac/pm-12473/collection-user-access-endpoint, so this should merge only after that branch lands.

Code Review Details
  • ❌ : Group-access rule checks Permissions.ManageUsers instead of Permissions.ManageGroups, diverging from BulkCollectionAuthorizationHandler.CanUpdateGroupAccessAsync
    • src/Api/AdminConsole/Authorization/Collections/CollectionGroupAuthorizationRules.cs:27
  • ⚠️ : EF ModifyGroupAccessAsync never bumps RevisionDate for upserted groups, unlike the MSSQL Collection_CreateOrUpdateAccessForMany path
    • src/Infrastructure.EntityFramework/AdminConsole/Repositories/CollectionRepository.cs:291
  • ⚠️ : Members of removed groups never get their AccountRevisionDate bumped, so their clients keep the collection cached (the user-side CollectionUser_DeleteMany handles this)
    • src/Sql/dbo/AdminConsole/Stored Procedures/CollectionGroup_DeleteMany.sql:15

Also noted, no action strictly required: CollectionGroupAuthorizationHandler, CollectionGroupAccessResource, and CollectionGroupAuthorizationRules are exact duplicates of their CollectionUser* counterparts apart from type names, which is what allowed the ManageUsers rule to carry over — a shared generic base would prevent future divergence.

return true;
}

if (allowAdminAccessToAllCollectionItems && organization is { Permissions.ManageUsers: true })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Group-access rule checks Permissions.ManageUsers instead of Permissions.ManageGroups

Details and fix

This looks like a copy-paste carry-over from CollectionUserAuthorizationRules.CanModifyUserAccess (the two classes are otherwise byte-identical). The established rule for modifying a group's collection access is ManageGroups — see BulkCollectionAuthorizationHandler:

private async Task<bool> CanUpdateGroupAccessAsync(...)
{
    if (await AllowAdminAccessToAllCollectionItems(org) && org?.Permissions.ManageGroups == true)

As written, once PM-41448 wires up the endpoint:

  • a Custom user with ManageUsers but not ManageGroups gains the ability to change group access (privilege escalation relative to the existing ModifyGroupAccess rule);
  • a Custom user with ManageGroups loses it (regression).

Suggested change:

if (allowAdminAccessToAllCollectionItems && organization is { Permissions.ManageGroups: true })

CollectionGroupAuthorizationRulesTests.CanModifyGroupAccess_WithManageUsersPermission_AllowAdminAccessTrue_Success and ..._CustomUserWithManageUsersPermission_AllowAdminAccessFalse_Failure were also copied over and will need renaming/retargeting to ManageGroups.

Related: CollectionGroupAuthorizationHandler, CollectionGroupAccessResource, and CollectionGroupAuthorizationRules are exact duplicates of their CollectionUser* counterparts apart from type names. Worth considering a shared generic base (e.g. parameterised on the permission to check) so a single authorization rule change can't silently diverge between the two paths again.

Comment on lines +291 to +297
var collections = await dbContext.Collections
.Where(c => collectionIds.Contains(c.Id))
.ToListAsync();
foreach (var collection in collections)
{
collection.RevisionDate = revisionDate;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT: EF path never bumps RevisionDate for upserted groups, diverging from MSSQL

Details and fix

Collection_CreateOrUpdateAccessForMany (used by the Dapper path) bumps the revision date of every group in @Groups:

-- Bump the revision date on all affected groups
UPDATE G SET G.[RevisionDate] = @RevisionDate
FROM [dbo].[Group] G
INNER JOIN @Groups GR ON G.[Id] = GR.[Id]
WHERE G.[OrganizationId] = @OrganizationId

The EF CreateOrUpdateAccessForManyAsync mirrors this (lines 945-957). ModifyGroupAccessAsync bumps only the removed groups, so on PostgreSQL/MySQL/SQLite an add/update of group access leaves Group.RevisionDate stale while SQL Server updates it.

Suggested addition alongside the collection bump:

if (upserts.Any())
{
    var upsertIds = upserts.Select(u => u.Id).ToList();
    var affectedGroups = await dbContext.Groups
        .Where(g => g.OrganizationId == organizationId && upsertIds.Contains(g.Id))
        .ToListAsync();
    foreach (var g in affectedGroups)
    {
        g.RevisionDate = revisionDate;
    }
}

Comment on lines +15 to +21
UPDATE
[dbo].[Group]
SET
[RevisionDate] = GETUTCDATE()
WHERE
[Id] IN (SELECT [Id] FROM @GroupIds)
END

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT: Members of removed groups never get their AccountRevisionDate bumped, so their clients won't re-sync

Details and fix

The user-side counterpart, CollectionUser_DeleteMany, ends with:

EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationUserIds] @OrganizationUserIds

This proc only bumps [Group].[RevisionDate]. The User_BumpAccountRevisionDateByCollectionIds call at the end of Collection_CreateOrUpdateAccessForMany (invoked afterwards in ModifyGroupAccessAsync) joins through CollectionUser/CollectionGroup, so users whose only access path was the just-deleted CollectionGroup row are no longer matched and are not bumped.

Net effect: a member who loses collection access through a group removal gets no sync signal, and their client keeps the collection and its ciphers cached locally until an unrelated change triggers a sync. Group_DeleteById avoids this by bumping the whole org before deleting.

Suggested addition before the DELETE (there is no User_BumpAccountRevisionDateByGroupIds proc, so resolve the org users first):

DECLARE @AffectedOrganizationUserIds [dbo].[GuidIdArray]
INSERT INTO @AffectedOrganizationUserIds ([Id])
SELECT DISTINCT GU.[OrganizationUserId]
FROM [dbo].[GroupUser] GU
WHERE GU.[GroupId] IN (SELECT [Id] FROM @GroupIds)

EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationUserIds] @AffectedOrganizationUserIds

The same gap exists in the EF ModifyGroupAccessAsync removal branch, and in util/Migrator/DbScripts/2026-08-04_00_AddCollectionGroupDeleteMany.sql.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant