feat: add SetOrganizationMemberRole RPC by whoAbhishekSah · Pull Request #1471 · raystack/frontier

coderabbitai[bot]

coderabbitai[bot]

coderabbitai[bot]

whoAbhishekSah

coderabbitai[bot]

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>

@whoAbhishekSah @claude

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>