238 lines
6.8 KiB
Python
238 lines
6.8 KiB
Python
import logging
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
|
from sqlalchemy.orm import Session
|
|
from starlette.requests import Request
|
|
|
|
from app.core.rbac import get_user_permission_names, require_permission
|
|
from app.db.database import get_db
|
|
from app.models.user import User
|
|
from app.repositories.rbac_repository import RbacRepository
|
|
from app.repositories.user_repository import UserRepository
|
|
from app.schemas.user import UserCreate, UserPasswordUpdate, UserResponse, UserUpdate
|
|
from app.services.audit_service import sanitize, write_audit_log
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(
|
|
prefix="/users",
|
|
tags=["Users"],
|
|
)
|
|
|
|
def get_user_or_404(db: Session, user_id: int) -> User:
|
|
db_user = UserRepository.get_by_id(db, user_id)
|
|
|
|
if db_user is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Benutzer nicht gefunden",
|
|
)
|
|
|
|
return db_user
|
|
|
|
|
|
def raise_conflict(field: str) -> None:
|
|
if field == "username":
|
|
detail = "Benutzername ist bereits vergeben"
|
|
else:
|
|
detail = "E-Mail ist bereits vergeben"
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail=detail,
|
|
)
|
|
|
|
|
|
def require_actor_permission(current_user: User, permission: str) -> None:
|
|
if permission not in get_user_permission_names(current_user):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Keine Berechtigung",
|
|
)
|
|
|
|
|
|
def apply_role(db: Session, user: UserCreate | UserUpdate) -> None:
|
|
role = None
|
|
if user.role_id is not None:
|
|
role = RbacRepository.get_role_by_id(db, user.role_id)
|
|
else:
|
|
role = RbacRepository.get_role_by_name(db, user.role)
|
|
|
|
if role is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Rolle nicht gefunden",
|
|
)
|
|
|
|
user.role_id = role.id
|
|
user.role = role.name
|
|
|
|
|
|
@router.get("", response_model=list[UserResponse])
|
|
@router.get("/", response_model=list[UserResponse], include_in_schema=False)
|
|
def get_users(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("users.read")),
|
|
):
|
|
logger.info("users.list", extra={"actor_user_id": current_user.id})
|
|
return UserRepository.get_all(db)
|
|
|
|
|
|
@router.get("/{user_id}", response_model=UserResponse)
|
|
def get_user(
|
|
user_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("users.read")),
|
|
):
|
|
logger.info(
|
|
"users.detail",
|
|
extra={"actor_user_id": current_user.id, "target_user_id": user_id},
|
|
)
|
|
return get_user_or_404(db, user_id)
|
|
|
|
|
|
@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
@router.post(
|
|
"/",
|
|
response_model=UserResponse,
|
|
status_code=status.HTTP_201_CREATED,
|
|
include_in_schema=False,
|
|
)
|
|
def create_user(
|
|
user: UserCreate,
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("users.create")),
|
|
):
|
|
require_actor_permission(current_user, "roles.assign")
|
|
apply_role(db, user)
|
|
conflict = UserRepository.find_conflict(
|
|
db,
|
|
username=user.username,
|
|
email=str(user.email),
|
|
)
|
|
|
|
if conflict is not None:
|
|
raise_conflict(conflict[0])
|
|
|
|
logger.info("users.create", extra={"actor_user_id": current_user.id})
|
|
created_user = UserRepository.create(db, user)
|
|
write_audit_log(
|
|
db,
|
|
action="users.create",
|
|
entity_type="users",
|
|
entity_id=created_user.id,
|
|
entity_label=created_user.username,
|
|
actor=current_user,
|
|
request=request,
|
|
after_data=created_user,
|
|
)
|
|
return created_user
|
|
|
|
|
|
@router.put("/{user_id}", response_model=UserResponse)
|
|
def update_user(
|
|
user_id: int,
|
|
user: UserUpdate,
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("users.update")),
|
|
):
|
|
db_user = get_user_or_404(db, user_id)
|
|
before_data = sanitize(db_user)
|
|
apply_role(db, user)
|
|
if user.role_id != db_user.role_id:
|
|
require_actor_permission(current_user, "roles.assign")
|
|
conflict = UserRepository.find_conflict(
|
|
db,
|
|
username=user.username,
|
|
email=str(user.email),
|
|
exclude_user_id=user_id,
|
|
)
|
|
|
|
if conflict is not None:
|
|
raise_conflict(conflict[0])
|
|
|
|
logger.info(
|
|
"users.update",
|
|
extra={"actor_user_id": current_user.id, "target_user_id": user_id},
|
|
)
|
|
updated_user = UserRepository.update(db, db_user, user)
|
|
write_audit_log(
|
|
db,
|
|
action="users.update",
|
|
entity_type="users",
|
|
entity_id=updated_user.id,
|
|
entity_label=updated_user.username,
|
|
actor=current_user,
|
|
request=request,
|
|
before_data=before_data,
|
|
after_data=updated_user,
|
|
)
|
|
return updated_user
|
|
|
|
|
|
@router.put("/{user_id}/password", response_model=UserResponse)
|
|
def update_user_password(
|
|
user_id: int,
|
|
password_update: UserPasswordUpdate,
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("users.password.update")),
|
|
):
|
|
db_user = get_user_or_404(db, user_id)
|
|
before_data = sanitize(db_user)
|
|
|
|
logger.info(
|
|
"users.password_update",
|
|
extra={"actor_user_id": current_user.id, "target_user_id": user_id},
|
|
)
|
|
updated_user = UserRepository.update_password(db, db_user, password_update)
|
|
write_audit_log(
|
|
db,
|
|
action="users.password_update",
|
|
entity_type="users",
|
|
entity_id=updated_user.id,
|
|
entity_label=updated_user.username,
|
|
actor=current_user,
|
|
request=request,
|
|
before_data=before_data,
|
|
after_data=updated_user,
|
|
metadata={"password_changed": True},
|
|
)
|
|
return updated_user
|
|
|
|
|
|
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
def delete_user(
|
|
user_id: int,
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_permission("users.delete")),
|
|
):
|
|
if current_user.id == user_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail="Der aktuell angemeldete Benutzer darf sich nicht selbst löschen",
|
|
)
|
|
|
|
db_user = get_user_or_404(db, user_id)
|
|
before_data = sanitize(db_user)
|
|
entity_label = db_user.username
|
|
|
|
logger.info(
|
|
"users.delete",
|
|
extra={"actor_user_id": current_user.id, "target_user_id": user_id},
|
|
)
|
|
UserRepository.delete(db, db_user)
|
|
write_audit_log(
|
|
db,
|
|
action="users.delete",
|
|
entity_type="users",
|
|
entity_id=user_id,
|
|
entity_label=entity_label,
|
|
actor=current_user,
|
|
request=request,
|
|
before_data=before_data,
|
|
)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|