feat: add SetOrganizationMemberRole RPC#1471
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR implements a new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes 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 |
There was a problem hiding this comment.
🧹 Nitpick comments (5)
core/organization/service.go (1)
407-423: Non-atomic delete-then-create leaves user without a role on partial failure.If any
policyService.Deletesucceeds but the subsequentpolicyService.Createfails (e.g., invalid roleID, SpiceDB unavailable), the user will be left with fewer or no org-level policies. This is a known architectural limitation (no distributed transaction between app DB and SpiceDB), but worth documenting in the method's godoc or adding a TODO for future transactional support.Consider adding a comment acknowledging this limitation:
// Note: Delete+Create is not atomic. If Create fails after Delete succeeds, // the user may be left without a policy. This is a known limitation until // cross-store transaction control is available.docs/analysis-role-change-bugs.md (2)
29-32: Add language identifiers to fenced code blocks.Static analysis flagged several code blocks without language specifiers. Adding language hints improves syntax highlighting and accessibility:
-``` +```text 1. DeletePolicy(policyId) ← for each existing policySimilar changes needed for blocks at lines 38, 54, 105, 160, and 178.
363-369: Add blank line before table.Per markdownlint MD058, tables should be surrounded by blank lines for consistent rendering:
**Files to modify**: + | File | Change | |------|--------|internal/api/v1beta1connect/organization.go (1)
543-543: Add request validation to match established codebase pattern.The
SetOrganizationMemberRoleRequesthas proto validation rules defined (minimum length of 3 runes forOrgId,UserId, andRoleId). Other handlers across the codebase (e.g.,user_pat.go,session.go,audit_record.go) consistently callrequest.Msg.Validate()before processing. Add the same validation here:Example validation call
if err := request.Msg.Validate(); err != nil { return nil, connect.NewError(connect.CodeInvalidArgument, err) }docs/sdk-iam-rpc-profile.md (1)
21-25: Add language specifier to fenced code block.The fenced code block is missing a language specifier. Add
```textor```plaintextto resolve the markdown linting warning.📝 Proposed fix
-``` +```text 1. listPolicies(resource, userId) → fetch existing policies 2. deletePolicy(policyId) x N → delete all existing policies 3. createPolicy(roleId, resource, principal) → create new policy with desired role</details> </blockquote></details> </blockquote></details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: Path: .coderabbit.yaml **Review profile**: CHILL **Plan**: Pro **Run ID**: `7d54427f-5739-4888-abcf-3fa71ef32f33` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between fec853b74ce91188250703dd517285b2cfdd9a22 and 26448595e7ce99a672f7dc0696fdb9983137de3e. </details> <details> <summary>⛔ Files ignored due to path filters (1)</summary> * `proto/v1beta1/frontier.pb.go` is excluded by `!**/*.pb.go` </details> <details> <summary>📒 Files selected for processing (11)</summary> * `Makefile` * `core/organization/errors.go` * `core/organization/service.go` * `docs/analysis-role-change-bugs.md` * `docs/sdk-iam-rpc-profile.md` * `internal/api/v1beta1connect/interfaces.go` * `internal/api/v1beta1connect/mocks/organization_service.go` * `internal/api/v1beta1connect/organization.go` * `pkg/server/connect_interceptors/authorization.go` * `proto/v1beta1/frontier.pb.validate.go` * `proto/v1beta1/frontierv1beta1connect/frontier.connect.go` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
daab379 to
b6b896c
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4671ca65-6dcf-4d0f-90cb-ea106044118b
⛔ Files ignored due to path filters (1)
proto/v1beta1/frontier.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (3)
Makefileproto/v1beta1/frontier.pb.validate.goproto/v1beta1/frontierv1beta1connect/frontier.connect.go
✅ Files skipped from review due to trivial changes (1)
- Makefile
Pull Request Test Coverage Report for Build 23531223348Details
💛 - Coveralls |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5656938d-84e8-42cc-9689-a6bdd6117882
📒 Files selected for processing (6)
cmd/serve.gocore/organization/mocks/role_service.gocore/organization/mocks/role_service_func.gocore/organization/service.gocore/organization/service_test.gointernal/api/v1beta1connect/organization.go
✅ Files skipped from review due to trivial changes (1)
- core/organization/mocks/role_service.go
🚧 Files skipped from review as they are similar to previous changes (1)
- core/organization/service.go
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
3b3b437 to
96cb676
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/api/v1beta1connect/organization.go (1)
543-545:⚠️ Potential issue | 🟠 MajorReject malformed
user_idvalues up front.Only the empty string is rejected here. A non-empty bad UUID still falls through to
organization.Service.SetMemberRoleand gets surfaced as a downstream 404/500 instead of theinvalid_argumentcontract implied byErrInvalidUserID.Suggested validation tightening
- // Validate user_id is not empty - if userID == "" { + // Validate user_id format + if userID == "" || !utils.IsValidUUID(userID) { return nil, connect.NewError(connect.CodeInvalidArgument, ErrInvalidUserID) }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2c546ad1-6cbe-43a5-b511-36d071b69224
📒 Files selected for processing (6)
cmd/serve.gocore/organization/mocks/role_service.gocore/organization/service.gocore/organization/service_test.gointernal/api/v1beta1connect/organization.gointernal/api/v1beta1connect/organization_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
- cmd/serve.go
- core/organization/service.go
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds atomic role assignment for organization members. Changes: - Add SetMemberRole method to organization service - Add ErrLastOwnerRole error for minimum owner constraint - Add SetOrganizationMemberRole handler (thin, delegates to service) - Add authorization check (policymanage permission) - Regenerate mocks The service handles: - Validate org, user exist - Check minimum owner constraint before demotion - Delete existing policies atomically - Create new policy with new role Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Explains why project/group policies are not touched during org role change. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add role existence validation in handler (returns invalid_argument) - Add empty user_id validation in handler - Fix owner role comparison to use UUID instead of role name - Add RoleService dependency to organization service for role lookups - Ensure min 1 owner check works correctly with UUID comparisons Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove adapter pattern and use role.Role type directly in organization service, avoiding unnecessary type conversion. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract helper functions for better readability: - getUserOrgPolicies: fetches user's org-level policies - validateMinOwnerConstraint: ensures at least 1 owner remains - replaceUserOrgPolicies: deletes old policies, creates new one Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Role existence check is business logic and belongs in the service layer, not the handler. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use service Get() method for org check (checks disabled state too) - Package org, user, role validation in one helper function Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cover all error branches: - empty user_id (invalid argument) - org not found - org disabled - user not found - role not found - role invalid id - last owner constraint (failed precondition) - unknown error (internal) - success case Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reject malformed user_id values with CodeInvalidArgument instead of letting them fall through to downstream 404/500 errors. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ErrLastOwnerRole is a different invariant from RemoveOrganizationUser's last-admin check. Using separate errors ensures clients receive the correct failure reason. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update proton commit to use UUID validation rules - Replace manual user_id UUID check with proto Validate() - All three fields (org_id, user_id, role_id) now validated as UUIDs - Update tests to use valid UUIDs and check error codes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove unnecessary ErrNotExist check in getUserOrgPolicies (policy service returns empty list, not error) - Return early if user is not currently an owner (optimization) - Simplify owner count check to len(ownerPolicies) <= 1 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SetOrganizationMemberRole should only change roles for existing members. Adding new members should go through AddOrganizationUsers. - Add ErrNotMember error - Check user has existing org policies before proceeding - Return FailedPrecondition if user is not a member Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Role must be either: - A global org role (OrgID="" and Scopes contains "app/organization") - An org-specific role for this org (OrgID matches target org) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Global roles have org_id as either empty string or nil UUID "00000000-0000-0000-0000-000000000000". Check for both. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update PROTON_COMMIT to ad22478c67a543d2359304e4cc2704a02a2bfc55 - Use utils.IsNullUUID() for cleaner global role check Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9f34814 to
eae912d
Compare
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Adds
SetOrganizationMemberRoleRPC for atomic role changes on org members. Replaces the SDK's multi-steplistPolicies → deletePolicy x N → createPolicyflow.policymanagepermission on orgHow it works
Validate request - Check that the organization, user, and role all exist. If org is disabled, return error. Validates role is valid for organization scope.
Get user's current org-level policies - Fetch policies for this user in this org. Project and group policies are intentionally not included because org owner/admin get implicit project access via SpiceDB relations (
org->project_get). Explicit project policies are for users who need project-specific access.Check minimum owner constraint - Before changing roles, verify this won't remove the last owner. We look up the owner role by name to get its UUID, then count how many owners exist. If the user is currently an owner and would be the last one, reject with
ErrLastOwnerRole.Replace policies - Delete all existing org-level policies for the user, then create a new policy with the specified role. This ensures one role per user per org.
Note: This assumes one role per user per org. If multiple roles need to be supported, consider accepting a list of roles or providing separate Add/Remove methods.
Testing
E2E tested with 31 test cases covering:
Permission & Authorization (Tests 1-21)
Role Scope Validation (Tests 22-31)
app/organizationscope) ✅app/projectscope) ❌app/groupscope) ❌All 31 tests pass.
Proto
Requires proton PR: raystack/proton#453
Related
🤖 Generated with Claude Code