mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 22:41:12 +02:00
Merge branch '3.1' into base-configs-3.0
This commit is contained in:
40 files changed
+3093
-204
No files matched your search
@@ -226,6 +226,18 @@ def create_default_roles(target, connection, **kw):
|
||||
{
|
||||
"description": "View an appliance",
|
||||
"name": "Appliance.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete an LLM model configuration",
|
||||
"name": "LLMConfig.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View an LLM model configuration",
|
||||
"name": "LLMConfig.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update an LLM model configuration",
|
||||
"name": "LLMConfig.Modify"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -295,7 +307,9 @@ def add_privileges_to_default_roles(target, connection, **kw):
|
||||
"Image.Audit",
|
||||
"Compute.Audit",
|
||||
"Appliance.Allocate",
|
||||
"Appliance.Audit"
|
||||
"Appliance.Audit",
|
||||
"LLMConfig.Audit",
|
||||
"LLMConfig.Modify"
|
||||
)
|
||||
|
||||
add_privileges_to_role(target, connection, "User", user_privileges)
|
||||
|
||||
@@ -156,6 +156,14 @@ class ResourcePoolsRepository(BaseRepository):
|
||||
Delete a resource pool.
|
||||
"""
|
||||
|
||||
# Get all resources in the pool first
|
||||
resources = await self.get_pool_resources(resource_pool_id)
|
||||
|
||||
# Delete all resource records
|
||||
for resource in resources:
|
||||
await self.delete_resource(resource.resource_id)
|
||||
|
||||
# Now delete the resource pool
|
||||
query = delete(models.ResourcePool).where(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
result = await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -203,6 +211,7 @@ class ResourcePoolsRepository(BaseRepository):
|
||||
resource_pool_db.resources.remove(resource)
|
||||
await self._db_session.commit()
|
||||
await self._db_session.refresh(resource_pool_db)
|
||||
|
||||
return resource_pool_db
|
||||
|
||||
async def get_pool_resources(self, resource_pool_id: UUID) -> List[models.Resource]:
|
||||
|
||||
@@ -230,6 +230,25 @@ class RbacRepository(BaseRepository):
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def get_aces_for_path(self, path: str) -> List[models.ACE]:
|
||||
"""
|
||||
Get all ACEs for a specific path (exact match or starting with path).
|
||||
|
||||
This method includes related user, group, and role information.
|
||||
"""
|
||||
|
||||
query = select(models.ACE).\
|
||||
where(
|
||||
(models.ACE.path == path) | (models.ACE.path.startswith(path + "/"))
|
||||
).\
|
||||
options(
|
||||
selectinload(models.ACE.user),
|
||||
selectinload(models.ACE.group),
|
||||
selectinload(models.ACE.role)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def check_ace_exists(self, path: str) -> bool:
|
||||
"""
|
||||
Check if an ACE exists.
|
||||
@@ -385,6 +404,83 @@ class RbacRepository(BaseRepository):
|
||||
pool_resources.extend(await self._get_resources_in_pools(group_aces))
|
||||
return list(set(pool_resources))
|
||||
|
||||
async def get_accessible_project_ids(
|
||||
self,
|
||||
user_id: UUID,
|
||||
privilege_name: str,
|
||||
all_project_ids: List[str]
|
||||
):
|
||||
"""
|
||||
Batch check which projects a user can access via direct ACE or resource pools.
|
||||
Performs 3 fixed DB queries regardless of project count.
|
||||
|
||||
Returns:
|
||||
(direct_ace_ids, pool_accessible_ids) where:
|
||||
- direct_ace_ids: projects the user has a direct ACE on (needs created_by filter)
|
||||
- pool_accessible_ids: projects in resource pools the user can access (no created_by filter)
|
||||
"""
|
||||
|
||||
user_aces = await self._get_user_aces(user_id, privilege_name)
|
||||
group_aces = await self._get_group_aces(user_id, privilege_name)
|
||||
|
||||
# Single query for all resources and their pool memberships
|
||||
query = select(models.Resource).options(selectinload(models.Resource.resource_pools))
|
||||
result = await self._db_session.execute(query)
|
||||
all_resources = result.scalars().all()
|
||||
|
||||
# Precompute pool_id -> set of project_ids
|
||||
pool_to_projects = {}
|
||||
for r in all_resources:
|
||||
if r.resource_type == "project":
|
||||
for pool in r.resource_pools:
|
||||
pool_to_projects.setdefault(str(pool.resource_pool_id), set()).add(str(r.resource_id))
|
||||
|
||||
# --- User ACE: direct path check ---
|
||||
direct_ace_ids = set()
|
||||
user_denied = set()
|
||||
|
||||
for pid in all_project_ids:
|
||||
path = f"/projects/{pid}"
|
||||
try:
|
||||
if self._check_path_with_aces(path, user_aces):
|
||||
direct_ace_ids.add(pid)
|
||||
except PermissionError:
|
||||
user_denied.add(pid)
|
||||
|
||||
# --- User ACE: pool check ---
|
||||
user_pool_ids = {ace_path.split("/")[2] for ace_path, _, ace_allowed, _ in user_aces
|
||||
if ace_path.startswith("/pools/") and ace_allowed}
|
||||
|
||||
pool_accessible_ids = set()
|
||||
for pool_id, project_ids in pool_to_projects.items():
|
||||
if pool_id in user_pool_ids:
|
||||
for pid in project_ids:
|
||||
if pid not in user_denied:
|
||||
pool_accessible_ids.add(pid)
|
||||
|
||||
# --- Group ACE: direct path check (skip already accessible or denied) ---
|
||||
for pid in all_project_ids:
|
||||
if pid in direct_ace_ids or pid in user_denied:
|
||||
continue
|
||||
path = f"/projects/{pid}"
|
||||
try:
|
||||
if self._check_path_with_aces(path, group_aces):
|
||||
direct_ace_ids.add(pid)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
# --- Group ACE: pool check ---
|
||||
group_pool_ids = {ace_path.split("/")[2] for ace_path, _, ace_allowed, _ in group_aces
|
||||
if ace_path.startswith("/pools/") and ace_allowed}
|
||||
|
||||
for pool_id, project_ids in pool_to_projects.items():
|
||||
if pool_id in group_pool_ids:
|
||||
for pid in project_ids:
|
||||
if pid not in user_denied and pid not in pool_accessible_ids:
|
||||
pool_accessible_ids.add(pid)
|
||||
|
||||
return direct_ace_ids, pool_accessible_ids
|
||||
|
||||
async def check_user_has_privilege(self, user_id: UUID, path: str, privilege_name: str) -> bool:
|
||||
"""
|
||||
Resource paths form a file system like tree and privileges can be inherited by paths down that tree
|
||||
@@ -409,22 +505,26 @@ class RbacRepository(BaseRepository):
|
||||
|
||||
aces = await self._get_user_aces(user_id, privilege_name)
|
||||
try:
|
||||
# Check regular ACEs first
|
||||
if self._check_path_with_aces(path, aces):
|
||||
# the user has an ACE matching the path and privilege, there is no need to check group ACEs
|
||||
return True
|
||||
# Then check resource pool ACEs
|
||||
if path_is_in_pool:
|
||||
if await self._get_resources_in_pools(aces, path):
|
||||
return True
|
||||
elif self._check_path_with_aces(path, aces):
|
||||
# the user has an ACE matching the path and privilege, there is no need to check group ACEs
|
||||
return True
|
||||
except PermissionError:
|
||||
return False
|
||||
|
||||
aces = await self._get_group_aces(user_id, privilege_name)
|
||||
try:
|
||||
# Check regular ACEs first
|
||||
if self._check_path_with_aces(path, aces):
|
||||
return True
|
||||
# Then check resource pool ACEs
|
||||
if path_is_in_pool:
|
||||
if await self._get_resources_in_pools(aces, path):
|
||||
return True
|
||||
elif self._check_path_with_aces(path, aces):
|
||||
return True
|
||||
except PermissionError:
|
||||
return False
|
||||
return False
|
||||
Reference in new issue
Block a user