fix(workspace): scope default workspace backfill to admins only

Restart-time backfill in WorkspaceSchemaMigration was inserting every
existing user into the default workspace and copying mate_user.role
('user'/'admin') into mate_workspace_member.role, whose valid domain is
{owner, admin, member, viewer}. Result: non-admin users assigned to
other workspaces were silently re-attached to the default workspace
with role='user', failing roleLevel() lookup and 403'ing on Agents.

- Filter the INSERT on u.role = 'admin' and hard-code the membership
  role to 'owner', removing the role-domain mismatch and the
  workspace-isolation violation in one change.
- Add V60__fix_invalid_workspace_member_roles.sql (h2 + mysql) to
  drop already-corrupted default-workspace rows for users who have a
  valid membership elsewhere, and downgrade the orphan rows to
  'member' so those users aren't locked out entirely.

Refs https://github.com/matevip/mateclaw/issues/29
This commit is contained in:
matevip 2026-04-28 23:40:21 +08:00
parent b4697f2806
commit a73b640c87
3 changed files with 58 additions and 5 deletions

View File

@ -60,26 +60,27 @@ public class WorkspaceSchemaMigration implements ApplicationRunner {
}
/**
* 确保所有现有用户都是默认工作区的成员
* Ensure all admin users have an owner-role membership in the default workspace.
* Non-admin users must be added explicitly via WorkspaceController.addMember.
*/
private void ensureDefaultWorkspaceMembership() {
try {
// 查找不在默认工作区中的用户
int inserted = jdbcTemplate.update("""
INSERT INTO mate_workspace_member (id, workspace_id, user_id, role, create_time, update_time, deleted)
SELECT u.id, 1, u.id, CASE WHEN u.role = 'admin' THEN 'owner' ELSE u.role END, NOW(), NOW(), 0
SELECT u.id, 1, u.id, 'owner', NOW(), NOW(), 0
FROM mate_user u
WHERE u.deleted = 0
AND u.role = 'admin'
AND NOT EXISTS (
SELECT 1 FROM mate_workspace_member wm
WHERE wm.workspace_id = 1 AND wm.user_id = u.id AND wm.deleted = 0
)
""");
if (inserted > 0) {
log.info("Added {} existing user(s) to default workspace", inserted);
log.info("Added {} admin user(s) as owner of default workspace", inserted);
}
} catch (DataAccessException e) {
log.debug("Skipping default workspace membership init: {}", e.getMessage());
log.debug("Skipping default workspace admin membership init: {}", e.getMessage());
}
}
}

View File

@ -0,0 +1,26 @@
-- RFC-076: clean up invalid workspace_member rows produced by the legacy
-- WorkspaceSchemaMigration.ensureDefaultWorkspaceMembership() insert
-- (issue: https://github.com/matevip/mateclaw/issues/29).
-- 1) For users who already have a valid membership in another workspace,
-- drop their illegal default-workspace membership.
DELETE FROM mate_workspace_member
WHERE workspace_id = 1
AND deleted = 0
AND role NOT IN ('owner', 'admin', 'member', 'viewer')
AND user_id IN (
SELECT user_id FROM (
SELECT user_id FROM mate_workspace_member
WHERE workspace_id <> 1
AND deleted = 0
AND role IN ('owner', 'admin', 'member', 'viewer')
) t
);
-- 2) For orphans whose only membership is the illegal default one,
-- normalize the role to 'member' so they don't get locked out entirely.
UPDATE mate_workspace_member
SET role = 'member', update_time = NOW()
WHERE workspace_id = 1
AND deleted = 0
AND role NOT IN ('owner', 'admin', 'member', 'viewer');

View File

@ -0,0 +1,26 @@
-- RFC-076: clean up invalid workspace_member rows produced by the legacy
-- WorkspaceSchemaMigration.ensureDefaultWorkspaceMembership() insert
-- (issue: https://github.com/matevip/mateclaw/issues/29).
-- 1) For users who already have a valid membership in another workspace,
-- drop their illegal default-workspace membership.
DELETE FROM mate_workspace_member
WHERE workspace_id = 1
AND deleted = 0
AND role NOT IN ('owner', 'admin', 'member', 'viewer')
AND user_id IN (
SELECT user_id FROM (
SELECT user_id FROM mate_workspace_member
WHERE workspace_id <> 1
AND deleted = 0
AND role IN ('owner', 'admin', 'member', 'viewer')
) t
);
-- 2) For orphans whose only membership is the illegal default one,
-- normalize the role to 'member' so they don't get locked out entirely.
UPDATE mate_workspace_member
SET role = 'member', update_time = NOW()
WHERE workspace_id = 1
AND deleted = 0
AND role NOT IN ('owner', 'admin', 'member', 'viewer');