fix(api): keep workspace owner consistent on ownership transfer - #41267
fix(api): keep workspace owner consistent on ownership transfer#41267GareArc wants to merge 3 commits into
Conversation
Transferring ownership from the console demoted the outgoing owner to admin, and under RBAC never wrote tenant_account_joins.role at all, so the enterprise dashboard and TenantService.is_owner kept resolving to the previous owner. - Legacy mode: demote the outgoing owner to normal instead of admin. - RBAC mode: resolve the outgoing owner from RBAC instead of the core DB row, strip only the owner role and keep the rest, falling back to no_access when nothing remains. - Move the core owner column in both modes, which also repairs rows left stale by earlier RBAC-mode transfers.
Pyrefly Type Coverage
|
The console owner-transfer controller test still asserted the outgoing owner became admin. Also drop a redundant str() call on Tenant.id, which is already typed str, flagged by pyrefly.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #41267 +/- ##
==========================================
- Coverage 86.73% 86.70% -0.04%
==========================================
Files 5429 5272 -157
Lines 307710 303573 -4137
Branches 61800 60692 -1108
==========================================
- Hits 266886 263199 -3687
+ Misses 35567 35118 -449
+ Partials 5257 5256 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR fixes inconsistencies in workspace ownership transfer by aligning the demotion behavior of the outgoing owner and ensuring the “core” workspace role column (tenant_account_joins.role) is kept consistent with RBAC ownership transfers.
Changes:
- Adjusts legacy ownership transfer demotion from
admin→normal(including the legacy branch ofRBACService.MemberRoles.replace). - In RBAC mode, resolves the outgoing owner via RBAC, strips only the owner role while preserving any other roles (fallback to
no_accesswhen needed), and repairs staletenant_account_joins.roleownership rows. - Adds/updates unit + container integration tests to cover RBAC demotion behavior and the DB owner-column swap (including stale-row repair).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| api/services/account_service.py | Updates ownership transfer logic (RBAC + core DB owner column handling) and role-id resolution helper extraction. |
| api/services/enterprise/rbac_service.py | Aligns legacy demotion behavior in MemberRoles.replace from admin to normal. |
| api/tests/unit_tests/services/test_account_service.py | Adds RBAC-enabled ownership transfer unit coverage (demotion outcomes + stale DB owner repair). |
| api/tests/unit_tests/services/enterprise/test_rbac_service.py | Updates expected legacy demotion behavior in RBAC service unit tests. |
| api/tests/test_containers_integration_tests/services/test_account_service.py | Updates integration assertions for outgoing owner demotion (admin → normal). |
| api/tests/test_containers_integration_tests/controllers/console/workspace/test_members.py | Updates console transfer-owner integration assertion for outgoing owner demotion (admin → normal). |
Suppressed comments (1)
api/services/account_service.py:1812
- In RBAC mode,
update_member_rolecurrently resolves the RBAC role ID usingTenantAccountRole.OWNERunconditionally, so updating a member toadmin/editor/normalwould still bind the owner RBAC role. This breaks role updates wheneverRBAC_ENABLEDis true.
Resolve the RBAC role ID from new_tenant_role instead of hard-coding OWNER.
resolved_role_id = AccountService._resolve_legacy_role_id(
tenant_id=str(tenant.id),
account_id=operator.id,
role=TenantAccountRole.OWNER,
)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Summary
Transferring workspace ownership from the console left the workspace in an inconsistent state, in two ways:
RBAC_ENABLED, the transfer only rebound the RBAC role and never wrotetenant_account_joins.role, so anything reading that column — the enterprise dashboard's workspace list/detail, andTenantService.is_owner, which guards the transfer endpoint itself — kept resolving to the previous owner.Changes
Who gets demoted, and to what
RBAC_ENABLED=false): the outgoing owner becomesnormalinstead ofadmin. Same correction applied inRBACService.MemberRoles.replace's legacy branch.get_rbac_workspace_owner_account_idrather than from the core DB row, which may be stale. Only the owner role is stripped; any other roles they hold are kept, falling back tono_access(restricted member) when nothing remains. This mirrorsrbacclient.TransferOwnerin dify-enterprise.Core owner column
tenant_account_joins.rolenow moves in both modes: whoever currently holdsowneris demoted tonormal, and the new owner is promoted. Keying the demotion off "whoever holds the column" rather than the RBAC-resolved account means workspaces already left inconsistent by an earlier RBAC-mode transfer are repaired on the next one, with no migration needed.Extracted
AccountService._resolve_role_id_by_tagfrom_resolve_legacy_role_idso a builtin role can be resolved by tag (no_accesshas noTenantAccountRoleequivalent).Testing
no_access) and asserting the core owner column swaps, including from a deliberately stale row.admin→normalchange.tests/unit_tests/services/test_account_service.py,tests/unit_tests/services/enterprise/test_rbac_service.py,tests/unit_tests/controllers/openapi/test_workspaces_members.py: 210 passed.tests/test_containers_integration_tests/services/test_account_service.py -k test_update_member_role: 3 passed.ruff checkandruff format --checkclean on all touched files.Checklist
make lint && make type-check(backend) andvp staged(frontend) to appease the lint gods