permissions.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. from dataclasses import dataclass
  2. from fastapi import HTTPException, status
  3. from sqlalchemy import text
  4. from sqlalchemy.orm import Session
  5. @dataclass(frozen=True)
  6. class Entitlement:
  7. hauptbetrieb_id: str
  8. standort_id: str
  9. abteilung: str | None
  10. role: str
  11. priority: int
  12. @dataclass(frozen=True)
  13. class Principal:
  14. username: str
  15. name: str
  16. email: str
  17. role: str
  18. auth_source: str
  19. csrf: str
  20. entitlements: tuple[Entitlement, ...]
  21. RIGHTS_SQL = text("""
  22. SELECT BR.Hauptbetrieb_ID,
  23. BR.Standort_ID,
  24. BR.Abteilung,
  25. BR.Rolle_Name,
  26. BR.Rolle_Prio
  27. FROM dbo.Benutzer_Rechte AS BR
  28. WHERE BR.Benutzer_ID = :username
  29. ORDER BY BR.Rolle_Prio DESC, BR.Rolle_Name ASC
  30. """)
  31. def load_entitlements(db: Session, username: str) -> tuple[Entitlement, ...]:
  32. rows = db.execute(RIGHTS_SQL, {"username": username}).mappings().all()
  33. return tuple(
  34. Entitlement(
  35. hauptbetrieb_id=str(row["Hauptbetrieb_ID"]),
  36. standort_id=str(row["Standort_ID"]),
  37. abteilung=row["Abteilung"],
  38. role=row["Rolle_Name"],
  39. priority=int(row["Rolle_Prio"]),
  40. )
  41. for row in rows
  42. )
  43. def highest_role(entitlements: tuple[Entitlement, ...]) -> str:
  44. if not entitlements:
  45. raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Keine Berechtigung für diese Anwendung")
  46. return sorted(entitlements, key=lambda item: (-item.priority, item.role))[0].role
  47. FORDERUNG_ACCESS_SQL = text("""
  48. SELECT TOP 1 1
  49. FROM dbo.Forderungen_Kopf AS F
  50. WHERE F.Client_DB = :client_db
  51. AND F.Beleg_Nr = :beleg_nr
  52. AND EXISTS (
  53. SELECT 1
  54. FROM dbo.Benutzer_Rechte AS BR
  55. WHERE BR.Benutzer_ID = :auth_username
  56. AND BR.Hauptbetrieb_ID = F.Hauptbetrieb_ID
  57. AND BR.Standort_ID = F.Standort_ID
  58. AND BR.Abteilung = F.Abteilung
  59. )
  60. """)
  61. def has_forderung_access(db: Session, principal: Principal, client_db: str, beleg_nr: str) -> bool:
  62. return (
  63. db.execute(
  64. FORDERUNG_ACCESS_SQL,
  65. {"client_db": client_db, "beleg_nr": beleg_nr, "auth_username": principal.username},
  66. ).first()
  67. is not None
  68. )
  69. def ensure_forderung_access(db: Session, principal: Principal, client_db: str, beleg_nr: str) -> None:
  70. if not has_forderung_access(db, principal, client_db, beleg_nr):
  71. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Forderung nicht gefunden")
  72. def current_principal(request) -> Principal:
  73. principal = getattr(request.state, "principal", None)
  74. if principal is None:
  75. raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Nicht angemeldet")
  76. return principal