Skip to content

feat(membership): add group membership management#1596

Open
whoAbhishekSah wants to merge 3 commits into
mainfrom
feat/group-membership-package
Open

feat(membership): add group membership management#1596
whoAbhishekSah wants to merge 3 commits into
mainfrom
feat/group-membership-package

Conversation

@whoAbhishekSah
Copy link
Copy Markdown
Member

@whoAbhishekSah whoAbhishekSah commented May 12, 2026

Summary

Adds the SetGroupMemberRole RPC 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 with ErrAlreadyMember, creates policy + matching group#owner/group#member relation, falling back to delete the policy if relation creation fails.
  • SetGroupMemberRole(...) — service + RPC. Rejects non-members with ErrNotMember, 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. Bundles group#org@organization + organization#member@group#member hierarchy relations with the initial owner add, so future group.Create can wire SpiceDB through one call.

Design notes

  • Principal type is restricted to app/user for groups today. The switch in validateGroupPrincipal is structured so adding app/serviceuser later is a one-case change with no API impact.
  • Audit events for added/role-changed cases were added under pkg/auditrecord and core/audit.
  • No existing call sites are migrated in this PR — group.Create, AddGroupUsers, and the deletion of group.addOwner/AddMember/AddUsers/addMemberPolicy follow in subsequent PRs to keep each diff reviewable.

Why this is safe to land alone

  • All new methods; nothing else calls them yet.
  • Existing org/project membership flows and AddGroupUsers/group creation behavior are untouched.

Test plan

  • go build ./...
  • go test ./core/membership/... ./internal/api/v1beta1connect/... ./core/audit/... ./pkg/auditrecord/...
  • New membership tests cover: not-found, principal-type rejection, disabled user, invalid group role, not-in-org, already-member, member add (member + owner role), policy cleanup on relation failure, role change (member↔owner with relation flip), skip-if-same, last-owner constraint, hierarchy linking on OnGroupCreated.
  • New handler tests cover the full error-mapping matrix for SetGroupMemberRole.
  • gofmt -l clean on changed files.

Follow-ups (separate PRs)

  • Migrate group.Create to OnGroupCreated; delete group.addOwner/addAsOrgMember/addOrgToGroup.
  • Migrate AddGroupUsers handler to loop AddGroupMember; delete group.AddMember/AddUsers/addMemberPolicy.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment May 12, 2026 7:47am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Warning

Rate limit exceeded

@whoAbhishekSah has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 9 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6a9c5864-e9d0-47e7-9cd2-7b7dad4d7872

📥 Commits

Reviewing files that changed from the base of the PR and between b7611c8 and 4eb055c.

⛔ Files ignored due to path filters (2)
  • proto/v1beta1/frontier.pb.go is excluded by !**/*.pb.go, !proto/**
  • proto/v1beta1/frontierv1beta1connect/frontier.connect.go is excluded by !proto/**
📒 Files selected for processing (12)
  • Makefile
  • core/audit/audit.go
  • core/membership/errors.go
  • core/membership/service.go
  • core/membership/service_test.go
  • internal/api/v1beta1connect/errors.go
  • internal/api/v1beta1connect/group.go
  • internal/api/v1beta1connect/group_test.go
  • internal/api/v1beta1connect/interfaces.go
  • internal/api/v1beta1connect/mocks/membership_service.go
  • pkg/auditrecord/consts.go
  • pkg/server/connect_interceptors/authorization.go

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coveralls
Copy link
Copy Markdown

coveralls commented May 12, 2026

Coverage Report for CI Build 25720876539

Coverage increased (+0.3%) to 42.352%

Details

  • Coverage increased (+0.3%) from the base build.
  • Patch coverage: 58 uncovered changes across 3 files (239 of 297 lines covered, 80.47%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
core/membership/service.go 247 199 80.57%
internal/api/v1beta1connect/group.go 46 40 86.96%
pkg/server/connect_interceptors/authorization.go 4 0 0.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 37583
Covered Lines: 15917
Line Coverage: 42.35%
Coverage Strength: 11.89 hits per line

💛 - Coveralls

@whoAbhishekSah
Copy link
Copy Markdown
Member Author

Manual Testing Results - SetGroupMemberRole RPC

Tested with multiple users and groups using the frontier-test CLI.

Test Environment

  • Org: pr1596-b557d6
  • Group1: Alice (owner), Bob (member), Charlie (member)
  • Group2: Alice (owner only)
  • Users: Alice, Bob, Charlie, Dave (in org), Eve (not in org)

Happy Path Tests

# Test Case Expected Actual Status
1 Promote Bob from member to owner {} (success) {} ✅ Pass
2 Demote Bob from owner to member (with Alice as owner) {} (success) {} ✅ Pass
3 Set same role (idempotent no-op) {} (success) {} ✅ Pass

Unhappy Path Tests

# Test Case Expected Actual Status
4 Invalid group_id (non-existent) error permission_denied ⚠️ Info
5 Invalid org_id (non-existent) not_found not_found: org doesn't exist ✅ Pass
6 Invalid principal_id (non-existent user) not_found not_found: user doesn't exist ✅ Pass
7 Invalid principal_type (app/serviceuser) invalid_argument invalid_argument: unsupported principal type ✅ Pass
8 User not in group (Dave) failed_precondition failed_precondition: principal is not a member of the resource ✅ Pass
9 Invalid role (org role for group) invalid_argument invalid_argument: role is not valid for group scope ✅ Pass
10 Demote last owner (Alice in group2) failed_precondition failed_precondition: group must have at least one owner... ✅ Pass

Authorization Tests

# Test Case Expected Actual Status
11 Non-owner (Bob) tries to change Charlie's role permission_denied permission_denied: not authorized ✅ Pass
12 Non-org-member (Eve) tries to change role permission_denied permission_denied: not authorized ✅ Pass
13 Set role for user not in org (Eve) error failed_precondition: principal is not a member of the resource ✅ Pass

Validation Tests

# Test Case Expected Actual Status
14 Invalid UUID format for group_id validation error validation error ✅ Pass
15 Empty principal_type validation error validation error ✅ Pass
16 Non-existent role_id not_found not_found: role id is invalid ✅ Pass

SpiceDB Relation Sync Tests

# Test Case Expected Actual Status
17 After promoting Bob to owner Bob can update group Bob can update group ✅ Pass
18 After demoting Bob to member Bob cannot update group permission_denied ✅ Pass

Summary

Category Passed Total
Happy Path 3 3
Unhappy Path 6 7
Authorization 3 3
Validation 3 3
Relation Sync 2 2
Total 17 18

Note: Test 4 returns permission_denied instead of not_found for invalid group_id. This is expected behavior - authorization is checked before resource existence.

The SpiceDB relation sync tests confirm that both policy and relation are updated atomically when roles change, fixing the leaky-relation issue.

whoAbhishekSah and others added 2 commits May 12, 2026 13:03
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>
raystack/proton#485 has merged. Re-pin from the feature-branch SHA
to the merge commit on proton main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread core/membership/service.go
Comment thread core/membership/service.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants