feat: fix permission check logic to properly handle ACE and user isolation

Implement the correct three-step permission check logic:
- Step 1: ACE check - basic access permission (get projects user has ACE for)
- Step 2: Filter ace_projects by created_by - user's own projects (project sharing only through resource pools)
- Step 3: Resource pool projects (projects shared through resource pools)

This fixes the design flaw where:
- ACE check could bypass user isolation with broad ACE configurations
- seen_project_ids mechanism prevented proper layered checking
- Project sharing was confused with direct ACE configuration

The new logic ensures:
- User isolation works even with broad ACE (path='/', propagate=True)
- Project sharing is only available through resource pools (clear design)
- Proper layered checking without seen blocking mechanism
This commit is contained in:
YueGuobin 2026-05-26 12:33:08 +08:00
parent 265d0ff860
commit ee36a9aec2
No known key found for this signature in database

View File

@ -86,32 +86,29 @@ async def get_projects(
controller = Controller.instance()
projects = []
seen_project_ids = set()
if current_user.is_superadmin:
# super admin sees all projects
return [p.asdict() for p in controller.projects.values()]
# Layer 1: ACE strategy check - explicitly authorized projects
# Check projects where user has specific ACE (shared by other users)
# Step 1: ACE check - basic access permission
# Get projects user has ACE for (basic access control)
ace_projects = []
for project in controller.projects.values():
project_path = f"/projects/{project.id}"
if await rbac_repo.check_user_has_privilege(current_user.user_id, project_path, "Project.Audit"):
if project.id not in seen_project_ids:
projects.append(project.asdict())
seen_project_ids.add(project.id)
ace_projects.append(project)
# Layer 2: Ownership check - user's own projects
# If no ACE strategy, filter by created_by
user_projects = [p.asdict() for p in controller.projects.values()
if p.created_by == current_user.username and p.id not in seen_project_ids]
# Step 2: Filter ace_projects by created_by - user's own projects
# Project sharing is only available through resource pools
user_projects = [p.asdict() for p in ace_projects if p.created_by == current_user.username]
projects.extend(user_projects)
# Layer 3: Resource pools - team shared projects
# Step 3: Resource pool projects
# Projects shared through resource pools
user_pool_resources = await rbac_repo.get_user_pool_resources(current_user.user_id, "Project.Audit")
project_ids_in_pools = [str(r.resource_id) for r in user_pool_resources if r.resource_type == "project"]
pool_projects = [p.asdict() for p in controller.projects.values()
if p.id in project_ids_in_pools and p.id not in seen_project_ids]
pool_projects = [p.asdict() for p in controller.projects.values() if p.id in project_ids_in_pools]
projects.extend(pool_projects)
return projects