feat(membership): add group membership management#1596
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR implements group membership management, extending the membership service with methods to add members to groups, change member roles with ownership constraints, and wire groups to their parent organization. The changes include domain logic, API handler, error types, audit events, and authorization rules. ChangesGroup Membership Management
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes The PR introduces coherent group membership logic across service and API layers with moderate complexity: domain-level policy/relation management and constraint validation, familiar error-mapping patterns in the handler, and standard mock/authorization wiring. The main review work is understanding the group-member lifecycle (addition with org-membership check, role changes with owner constraint, relation management via SpiceDB) and verifying test coverage. Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Coverage Report for CI Build 25776634343Coverage increased (+0.3%) to 42.356%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Manual Testing Results - SetGroupMemberRole RPCTested with multiple users and groups using the frontier-test CLI. Test Environment
Happy Path Tests
Unhappy Path Tests
Authorization Tests
Validation Tests
SpiceDB Relation Sync Tests
Summary
The SpiceDB relation sync tests confirm that both policy and relation are updated atomically when roles change, fixing the leaky-relation issue. |
ca8efde to
05c5097
Compare
Introduces the SetGroupMemberRole RPC and three service methods on the membership package: AddGroupMember, SetGroupMemberRole, and OnGroupCreated. These manage policy + SpiceDB relation atomically and keep them in sync, fixing the leaky-relation pattern at the group layer. - AddGroupMember validates org membership of the principal and rejects duplicates with ErrAlreadyMember (service-only, no proto). - SetGroupMemberRole rejects non-members with ErrNotMember and enforces a min-owner constraint (ErrLastGroupOwnerRole) on demotion. - OnGroupCreated bundles the group<->org hierarchy relations with the initial owner add, so group.Create can wire SpiceDB with one call. - Principal validation is restricted to app/user; the switch is kept extensible for future principal types. Audit events are added for both the added and role-changed cases. No call sites are migrated yet — group.Create, AddGroupUsers, and the deletion of legacy group service methods will follow in subsequent PRs. PROTON_COMMIT is temporarily pinned to the feature-branch SHA on raystack/proton#485; it will be re-pinned to the merge commit once that PR lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The authorization interceptor denies any procedure not in its map. Without this entry the new RPC returned PermissionDenied at the interceptor before reaching the handler. Uses GroupNamespace + UpdatePermission, matching AddGroupUsers/RemoveGroupUser. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4eb055c to
291b0fc
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
core/membership/service_test.go (1)
1587-1597: ⚡ Quick winAdd regression tests for hierarchy compensation paths.
Please add
OnGroupCreatedcases for: (1) second relation create failure with rollback of the first relation, and (2)AddGroupMemberfailure after successful hierarchy links with rollback of both links.internal/api/v1beta1connect/group.go (1)
487-513: ⚡ Quick winOnly log the unexpected branch here.
LogServiceErrorcurrently runs for every mapped client error (not_found,invalid_argument,failed_precondition) before the switch. That will turn normal validation/membership failures into error-log noise and make real server faults harder to spot. Move the log call into thedefaultbranch, or otherwise limit it to the paths you truly treat as internal failures.♻️ Suggested change
if err := h.membershipService.SetGroupMemberRole(ctx, groupID, principalID, principalType, roleID); err != nil { - errorLogger.LogServiceError(ctx, request, "SetGroupMemberRole", err, - "group_id", groupID, - "principal_id", principalID, - "principal_type", principalType, - "role_id", roleID) - switch { case errors.Is(err, group.ErrNotExist), errors.Is(err, group.ErrInvalidID), errors.Is(err, group.ErrInvalidUUID): return nil, connect.NewError(connect.CodeNotFound, ErrGroupNotFound) @@ case errors.Is(err, membership.ErrLastGroupOwnerRole): return nil, connect.NewError(connect.CodeFailedPrecondition, ErrLastGroupOwnerRole) default: + errorLogger.LogServiceError(ctx, request, "SetGroupMemberRole", err, + "group_id", groupID, + "principal_id", principalID, + "principal_type", principalType, + "role_id", roleID) return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError) } }pkg/server/connect_interceptors/authorization.go (1)
477-480: ⚡ Quick winPlease add an interceptor regression test for this route.
This fix lives entirely in
authorizationValidationMap, and the original failure mode was the interceptor returningpermission_deniedbefore the handler ran. A handler-only test suite won't catch that regression if this entry is removed or wired to the wrong permission later.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d40d9e97-70c8-43db-affb-591942055065
📒 Files selected for processing (11)
core/audit/audit.gocore/membership/errors.gocore/membership/service.gocore/membership/service_test.gointernal/api/v1beta1connect/errors.gointernal/api/v1beta1connect/group.gointernal/api/v1beta1connect/group_test.gointernal/api/v1beta1connect/interfaces.gointernal/api/v1beta1connect/mocks/membership_service.gopkg/auditrecord/consts.gopkg/server/connect_interceptors/authorization.go
When the second hierarchy relation or the owner add fails, the partially written relations are best-effort cleaned up so a group isn't left in a half-linked or unowned state. Adds unlinkGroupFromOrg helper. Also fixes the SetGroupMemberRole handler test for unsupported principal type: previously it sent app/user and forced the service to return an error, which only exercised error mapping. Now it sends app/serviceuser and asserts the handler forwards that value unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds the
SetGroupMemberRoleRPC and three new service methods on the membership package that fix the long-standing leaky-relation issue for groups by managing policy and SpiceDB relation in sync.AddGroupMember(groupID, principalID, principalType, roleID)— service-only (no proto). Validates the principal is a member of the group's parent org, rejects existing members withErrAlreadyMember, creates policy + matchinggroup#owner/group#memberrelation, falling back to delete the policy if relation creation fails.SetGroupMemberRole(...)— service + RPC. Rejects non-members withErrNotMember, enforces min-owner constraint (ErrLastGroupOwnerRole) on demotion, replaces both policy and relation atomically (matches the org pattern in Add SetOrganizationMemberRole RPC to replace client-side policy manipulation #1459).OnGroupCreated(groupID, orgID, creatorID, creatorType)— service-only. Bundlesgroup#org@organization+organization#member@group#memberhierarchy relations with the initial owner add, so futuregroup.Createcan wire SpiceDB through one call.Design notes
app/userfor groups today. The switch invalidateGroupPrincipalis structured so addingapp/serviceuserlater is a one-case change with no API impact.pkg/auditrecordandcore/audit.group.Create,AddGroupUsers, and the deletion ofgroup.addOwner/AddMember/AddUsers/addMemberPolicyfollow in subsequent PRs to keep each diff reviewable.Why this is safe to land alone
AddGroupUsers/group creation behavior are untouched.Test plan
go build ./...go test ./core/membership/... ./internal/api/v1beta1connect/... ./core/audit/... ./pkg/auditrecord/...OnGroupCreated.SetGroupMemberRole.gofmt -lclean on changed files.Follow-ups (separate PRs)
group.CreatetoOnGroupCreated; deletegroup.addOwner/addAsOrgMember/addOrgToGroup.AddGroupUsershandler to loopAddGroupMember; deletegroup.AddMember/AddUsers/addMemberPolicy.