bmc_hub/app/auth/backend/admin.py

187 lines
6.5 KiB
Python
Raw Normal View History

"""
Auth Admin API - Users, Groups, Permissions management
"""
from fastapi import APIRouter, HTTPException, status, Depends
from app.core.auth_dependencies import require_permission
from app.core.auth_service import AuthService
from app.core.database import execute_query, execute_query_single, execute_insert, execute_update
from app.models.schemas import UserAdminCreate, UserGroupsUpdate, GroupCreate, GroupPermissionsUpdate, UserTwoFactorResetRequest
import logging
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/admin/users", dependencies=[Depends(require_permission("users.manage"))])
async def list_users():
users = execute_query(
"""
SELECT u.user_id, u.username, u.email, u.full_name,
u.is_active, u.is_superadmin, u.is_2fa_enabled,
u.telefoni_extension, u.telefoni_aktiv, u.telefoni_phone_ip, u.telefoni_phone_username,
u.created_at, u.last_login_at,
COALESCE(array_remove(array_agg(g.name), NULL), ARRAY[]::varchar[]) AS groups
FROM users u
LEFT JOIN user_groups ug ON u.user_id = ug.user_id
LEFT JOIN groups g ON ug.group_id = g.id
GROUP BY u.user_id
ORDER BY u.user_id
"""
)
return users
@router.post("/admin/users", status_code=status.HTTP_201_CREATED, dependencies=[Depends(require_permission("users.manage"))])
async def create_user(payload: UserAdminCreate):
existing = execute_query_single(
"SELECT user_id FROM users WHERE username = %s OR email = %s",
(payload.username, payload.email)
)
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Username or email already exists"
)
try:
password_hash = AuthService.hash_password(payload.password)
except Exception as exc:
logger.error("❌ Password hash failed: %s", exc)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Kunne ikke hashe adgangskoden"
) from exc
user_id = execute_insert(
"""
INSERT INTO users (username, email, password_hash, full_name, is_superadmin, is_active)
VALUES (%s, %s, %s, %s, %s, %s) RETURNING user_id
""",
(payload.username, payload.email, password_hash, payload.full_name, payload.is_superadmin, payload.is_active)
)
if payload.group_ids:
for group_id in payload.group_ids:
execute_update(
"""
INSERT INTO user_groups (user_id, group_id)
VALUES (%s, %s) ON CONFLICT DO NOTHING
""",
(user_id, group_id)
)
logger.info("✅ User created via admin: %s (ID: %s)", payload.username, user_id)
return {"user_id": user_id}
@router.put("/admin/users/{user_id}/groups", dependencies=[Depends(require_permission("users.manage"))])
async def update_user_groups(user_id: int, payload: UserGroupsUpdate):
user = execute_query_single("SELECT user_id FROM users WHERE user_id = %s", (user_id,))
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
execute_update("DELETE FROM user_groups WHERE user_id = %s", (user_id,))
for group_id in payload.group_ids:
execute_update(
"""
INSERT INTO user_groups (user_id, group_id)
VALUES (%s, %s) ON CONFLICT DO NOTHING
""",
(user_id, group_id)
)
return {"message": "Groups updated"}
@router.post("/admin/users/{user_id}/2fa/reset")
async def reset_user_2fa(
user_id: int,
payload: UserTwoFactorResetRequest,
current_user: dict = Depends(require_permission("users.manage"))
):
ok = AuthService.admin_reset_user_2fa(user_id)
if not ok:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
reason = (payload.reason or "").strip()
if reason:
logger.info(
"✅ Admin reset 2FA for user_id=%s by %s (reason: %s)",
user_id,
current_user.get("username"),
reason
)
else:
logger.info(
"✅ Admin reset 2FA for user_id=%s by %s",
user_id,
current_user.get("username")
)
return {"message": "2FA reset"}
@router.get("/admin/groups", dependencies=[Depends(require_permission("users.manage"))])
async def list_groups():
groups = execute_query(
"""
SELECT g.id, g.name, g.description,
COALESCE(array_remove(array_agg(p.code), NULL), ARRAY[]::varchar[]) AS permissions
FROM groups g
LEFT JOIN group_permissions gp ON g.id = gp.group_id
LEFT JOIN permissions p ON gp.permission_id = p.id
GROUP BY g.id
ORDER BY g.id
"""
)
return groups
@router.post("/admin/groups", status_code=status.HTTP_201_CREATED, dependencies=[Depends(require_permission("permissions.manage"))])
async def create_group(payload: GroupCreate):
existing = execute_query_single("SELECT id FROM groups WHERE name = %s", (payload.name,))
if existing:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Group already exists")
group_id = execute_insert(
"""
INSERT INTO groups (name, description)
VALUES (%s, %s) RETURNING id
""",
(payload.name, payload.description)
)
return {"group_id": group_id}
@router.put("/admin/groups/{group_id}/permissions", dependencies=[Depends(require_permission("permissions.manage"))])
async def update_group_permissions(group_id: int, payload: GroupPermissionsUpdate):
group = execute_query_single("SELECT id FROM groups WHERE id = %s", (group_id,))
if not group:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found")
execute_update("DELETE FROM group_permissions WHERE group_id = %s", (group_id,))
for permission_id in payload.permission_ids:
execute_update(
"""
INSERT INTO group_permissions (group_id, permission_id)
VALUES (%s, %s) ON CONFLICT DO NOTHING
""",
(group_id, permission_id)
)
return {"message": "Permissions updated"}
@router.get("/admin/permissions", dependencies=[Depends(require_permission("permissions.manage"))])
async def list_permissions():
permissions = execute_query(
"""
SELECT id, code, description, category
FROM permissions
ORDER BY category, code
"""
)
return permissions