Complete resource pool support for projects

This commit is contained in:
grossmj committed 2023-09-11 18:15:03 +07:00
1 parent d53ef175f8
commit a95dda0d1d
6 files changed
+256 -30

No files matched your search

+8 -1
View File
@@ -38,6 +38,7 @@ from gns3server.db.repositories.users import UsersRepository
from gns3server.db.repositories.rbac import RbacRepository
from gns3server.db.repositories.images import ImagesRepository
from gns3server.db.repositories.templates import TemplatesRepository
from gns3server.db.repositories.pools import ResourcePoolsRepository
from .dependencies.database import get_repository
from .dependencies.rbac import has_privilege
@@ -57,7 +58,8 @@ async def endpoints(
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
) -> List[dict]:
"""
List all endpoints to be used in ACL entries.
@@ -128,6 +130,11 @@ async def endpoints(
for template in templates:
add_to_endpoints(f"/templates/{template.template_id}", f'Template "{template.name}"', "template")
# resource pools
add_to_endpoints("/pools", "All resource pools", "pool")
pools = await pools_repo.get_resource_pools()
for pool in pools:
add_to_endpoints(f"/pools/{pool.resource_pool_id}", f'Resource pool "{pool.name}"', "pool")
return endpoints
+24 -4
View File
@@ -47,9 +47,11 @@ from gns3server.utils.asyncio import aiozipstream
from gns3server.utils.path import is_safe_path
from gns3server.db.repositories.templates import TemplatesRepository
from gns3server.db.repositories.rbac import RbacRepository
from gns3server.db.repositories.pools import ResourcePoolsRepository
from gns3server.services.templates import TemplatesService
from .dependencies.rbac import has_privilege, has_privilege_on_websocket
from .dependencies.authentication import get_current_active_user
from .dependencies.database import get_repository
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project"}}
@@ -69,10 +71,13 @@ def dep_project(project_id: UUID) -> Project:
@router.get(
"",
response_model=List[schemas.Project],
response_model_exclude_unset=True,
dependencies=[Depends(has_privilege("Project.Audit"))]
response_model_exclude_unset=True
)
async def get_projects() -> List[schemas.Project]:
async def get_projects(
current_user: schemas.User = Depends(get_current_active_user),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
) -> List[schemas.Project]:
"""
Return all projects.
@@ -80,7 +85,22 @@ async def get_projects() -> List[schemas.Project]:
"""
controller = Controller.instance()
return [p.asdict() for p in controller.projects.values()]
projects = []
if current_user.is_superadmin:
# super admin sees all projects
return [p.asdict() for p in controller.projects.values()]
elif await rbac_repo.check_user_has_privilege(current_user.user_id, "/projects", "Project.Audit"):
# user with Project.Audit privilege on '/projects' sees all projects except those in resource pools
project_ids_in_pools = [str(r.resource_id) for r in await pools_repo.get_resources() if r.resource_type == "project"]
projects.extend([p.asdict() for p in controller.projects.values() if p.id not in project_ids_in_pools])
# user with Project.Audit privilege on resource pools sees the projects in these 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"]
projects.extend([p.asdict() for p in controller.projects.values() if p.id in project_ids_in_pools])
return projects
@router.post(
+17
View File
@@ -81,6 +81,23 @@ async def create_role(
return await rbac_repo.create_role(role_create)
@router.get(
"/privileges",
response_model=List[schemas.Privilege],
dependencies=[Depends(has_privilege("Role.Audit"))]
)
async def get_privileges(
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
) -> List[schemas.Privilege]:
"""
Get all available privileges.
Required privilege: Role.Audit
"""
return await rbac_repo.get_privileges()
@router.get(
"/{role_id}",
response_model=schemas.Role,
+90 -25
View File
@@ -309,6 +309,75 @@ class RbacRepository(BaseRepository):
return True # only allow if the path is the original path or the ACE is set to propagate
return False
async def _get_resources_in_pools(self, aces, path: str = None) -> List[models.Resource]:
"""
Get all resources in pools.
"""
pool_resources = []
for ace_path, ace_propagate, ace_allowed, ace_privilege in aces:
if ace_path.startswith("/pool"):
resource_pool_id = ace_path.split("/")[2]
query = select(models.Resource). \
join(models.Resource.resource_pools). \
filter(models.ResourcePool.resource_pool_id == resource_pool_id)
result = await self._db_session.execute(query)
resources = result.scalars().all()
for resource in resources:
# we only support projects in resource pools for now
if resource.resource_type == "project":
if path:
if path.startswith(f"/projects/{resource.resource_id}"):
pool_resources.append(resource)
else:
pool_resources.append(resource)
return pool_resources
async def _get_user_aces(self, user_id: UUID, privilege_name: str):
"""
Retrieve all user ACEs matching the user_id and privilege name.
"""
query = select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name).\
join(models.Privilege.roles).\
join(models.Role.acl_entries).\
join(models.ACE.user). \
filter(models.User.user_id == user_id).\
filter(models.Privilege.name == privilege_name).\
order_by(models.ACE.path.desc())
result = await self._db_session.execute(query)
return result.all()
async def _get_group_aces(self, user_id: UUID, privilege_name: str):
"""
Retrieve all group ACEs matching the user_id and privilege name.
"""
query = select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name). \
join(models.Privilege.roles). \
join(models.Role.acl_entries). \
join(models.ACE.group). \
join(models.UserGroup.users).\
filter(models.User.user_id == user_id). \
filter(models.Privilege.name == privilege_name)
result = await self._db_session.execute(query)
return result.all()
async def get_user_pool_resources(self, user_id: UUID, privilege_name: str) -> List[models.Resource]:
"""
Get all resources in pools belonging to a user and groups
"""
user_aces = await self._get_user_aces(user_id, privilege_name)
pool_resources = await self._get_resources_in_pools(user_aces)
group_aces = await self._get_group_aces(user_id, privilege_name)
pool_resources.extend(await self._get_resources_in_pools(group_aces))
return list(set(pool_resources))
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
@@ -321,38 +390,34 @@ class RbacRepository(BaseRepository):
* Privileges on deeper levels replace those inherited from an upper level.
"""
# retrieve all user ACEs matching the user_id and privilege name
query = select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name).\
join(models.Privilege.roles).\
join(models.Role.acl_entries).\
join(models.ACE.user). \
filter(models.User.user_id == user_id).\
filter(models.Privilege.name == privilege_name).\
order_by(models.ACE.path.desc())
query = select(models.Resource)
result = await self._db_session.execute(query)
aces = result.all()
resources = result.scalars().all()
projects_in_pools = [f"/projects/{r.resource_id}" for r in resources if r.resource_type == "project"]
path_is_in_pool = False
for project_in_pool in projects_in_pools:
if path.startswith(project_in_pool):
path_is_in_pool = True
break
aces = await self._get_user_aces(user_id, privilege_name)
try:
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
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
# retrieve all group ACEs matching the user_id and privilege name
query = select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name). \
join(models.Privilege.roles). \
join(models.Role.acl_entries). \
join(models.ACE.group). \
join(models.UserGroup.users).\
filter(models.User.user_id == user_id). \
filter(models.Privilege.name == privilege_name)
result = await self._db_session.execute(query)
aces = result.all()
aces = await self._get_group_aces(user_id, privilege_name)
try:
return self._check_path_with_aces(path, 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