Basic functional RBAC support.

This commit is contained in:
grossmj committed 2021-05-27 17:28:44 +09:30
1 parent 6d4da98b8e
commit fbc47598d9
19 files changed
+527 -92

No files matched your search

+1 -1
View File
@@ -55,7 +55,7 @@ router.include_router(
)
router.include_router(
roles.router,
permissions.router,
dependencies=[Depends(get_current_active_user)],
prefix="/permissions",
tags=["Permissions"]
@@ -15,13 +15,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from fastapi import Depends, HTTPException, status
from fastapi import Request, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from gns3server import schemas
from gns3server.db.repositories.users import UsersRepository
from gns3server.db.repositories.rbac import RbacRepository
from gns3server.services import auth_service
from .database import get_repository
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login")
@@ -42,7 +42,11 @@ async def get_user_from_token(
return user
async def get_current_active_user(current_user: schemas.User = Depends(get_user_from_token)) -> schemas.User:
async def get_current_active_user(
request: Request,
current_user: schemas.User = Depends(get_user_from_token),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
) -> schemas.User:
# Super admin is always authorized
if current_user.is_superadmin:
@@ -54,4 +58,19 @@ async def get_current_active_user(current_user: schemas.User = Depends(get_user_
detail="Not an active user",
headers={"WWW-Authenticate": "Bearer"},
)
# remove the prefix (e.g. "/v3") from URL path
if request.url.path.startswith("/v3"):
path = request.url.path[len("/v3"):]
else:
path = request.url.path
authorized = await rbac_repo.check_user_is_authorized(current_user.user_id, request.method, path)
if not authorized:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"User is not authorized '{current_user.user_id}' on {request.method} '{path}'",
headers={"WWW-Authenticate": "Bearer"},
)
return current_user
+2 -2
View File
@@ -99,7 +99,7 @@ async def update_user_group(
if not user_group:
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
if not user_group.is_updatable:
if user_group.builtin:
raise ControllerForbiddenError(f"User group '{user_group_id}' cannot be updated")
return await users_repo.update_user_group(user_group_id, user_group_update)
@@ -121,7 +121,7 @@ async def delete_user_group(
if not user_group:
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
if not user_group.is_updatable:
if user_group.builtin:
raise ControllerForbiddenError(f"User group '{user_group_id}' cannot be deleted")
success = await users_repo.delete_user_group(user_group_id)
@@ -60,8 +60,9 @@ async def create_permission(
Create a new permission.
"""
# if await rbac_repo.get_role_by_path(role_create.name):
# raise ControllerBadRequestError(f"Role '{role_create.name}' already exists")
if await rbac_repo.check_permission_exists(permission_create):
raise ControllerBadRequestError(f"Permission '{permission_create.methods} {permission_create.path} "
f"{permission_create.action}' already exists")
return await rbac_repo.create_permission(permission_create)
@@ -95,9 +96,6 @@ async def update_permission(
if not permission:
raise ControllerNotFoundError(f"Permission '{permission_id}' not found")
#if not user_group.is_updatable:
# raise ControllerForbiddenError(f"User group '{user_group_id}' cannot be updated")
return await rbac_repo.update_permission(permission_id, permission_update)
@@ -114,9 +112,6 @@ async def delete_permission(
if not permission:
raise ControllerNotFoundError(f"Permission '{permission_id}' not found")
#if not user_group.is_updatable:
# raise ControllerForbiddenError(f"User group '{user_group_id}' cannot be deleted")
success = await rbac_repo.delete_permission(permission_id)
if not success:
raise ControllerNotFoundError(f"Permission '{permission_id}' could not be deleted")
+15 -2
View File
@@ -47,6 +47,10 @@ from gns3server.controller.export_project import export_project as export_contro
from gns3server.utils.asyncio import aiozipstream
from gns3server.utils.path import is_safe_path
from gns3server.config import Config
from gns3server.db.repositories.rbac import RbacRepository
from .dependencies.authentication import get_current_active_user
from .dependencies.database import get_repository
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project"}}
@@ -82,13 +86,18 @@ def get_projects() -> List[schemas.Project]:
response_model_exclude_unset=True,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create project"}},
)
async def create_project(project_data: schemas.ProjectCreate) -> schemas.Project:
async def create_project(
project_data: schemas.ProjectCreate,
current_user: schemas.User = Depends(get_current_active_user),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
) -> schemas.Project:
"""
Create a new project.
"""
controller = Controller.instance()
project = await controller.add_project(**jsonable_encoder(project_data, exclude_unset=True))
await rbac_repo.add_permission_to_user(current_user.user_id, f"/projects/{project.id}/*")
return project.asdict()
@@ -115,7 +124,10 @@ async def update_project(
@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_project(project: Project = Depends(dep_project)) -> None:
async def delete_project(
project: Project = Depends(dep_project),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
) -> None:
"""
Delete a project.
"""
@@ -123,6 +135,7 @@ async def delete_project(project: Project = Depends(dep_project)) -> None:
controller = Controller.instance()
await project.delete()
controller.remove_project(project)
await rbac_repo.delete_all_permissions_matching_path(f"/projects/{project.id}")
@router.get("/{project_id}/stats")
+4 -4
View File
@@ -95,8 +95,8 @@ async def update_role(
if not role:
raise ControllerNotFoundError(f"Role '{role_id}' not found")
#if not user_group.is_updatable:
# raise ControllerForbiddenError(f"User group '{user_group_id}' cannot be updated")
if role.builtin:
raise ControllerForbiddenError(f"Role '{role_id}' cannot be updated")
return await rbac_repo.update_role(role_id, role_update)
@@ -114,8 +114,8 @@ async def delete_role(
if not role:
raise ControllerNotFoundError(f"Role '{role_id}' not found")
#if not user_group.is_updatable:
# raise ControllerForbiddenError(f"User group '{user_group_id}' cannot be deleted")
if role.builtin:
raise ControllerForbiddenError(f"Role '{role_id}' cannot be deleted")
success = await rbac_repo.delete_role(role_id)
if not success:
+18 -9
View File
@@ -88,6 +88,24 @@ async def authenticate(
return token
@router.get("/me", response_model=schemas.User)
async def get_logged_in_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User:
"""
Get the current active user.
"""
return current_user
@router.get("/me", response_model=schemas.User)
async def get_logged_in_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User:
"""
Get the current active user.
"""
return current_user
@router.get("", response_model=List[schemas.User], dependencies=[Depends(get_current_active_user)])
async def get_users(
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
@@ -178,15 +196,6 @@ async def delete_user(
raise ControllerNotFoundError(f"User '{user_id}' could not be deleted")
@router.get("/me/", response_model=schemas.User)
async def get_current_active_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User:
"""
Get the current active user.
"""
return current_user
@router.get(
"/{user_id}/groups",
dependencies=[Depends(get_current_active_user)],