group.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. """Group model for permission-based access control."""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import TYPE_CHECKING
  5. from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Table, func
  6. from sqlalchemy.orm import Mapped, mapped_column, relationship
  7. from sqlalchemy.types import JSON
  8. from backend.app.core.database import Base
  9. if TYPE_CHECKING:
  10. from backend.app.models.user import User
  11. # Many-to-many association table between users and groups
  12. user_groups = Table(
  13. "user_groups",
  14. Base.metadata,
  15. Column("user_id", Integer, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
  16. Column("group_id", Integer, ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True),
  17. )
  18. # Printers a group with ``restrict_printers`` set is allowed to see and control
  19. # (#1727). Rows are only meaningful while the flag is on; turning it off keeps
  20. # them so the selection survives toggling. SQLite doesn't enforce the FK
  21. # cascades, so printer and group deletes remove their rows explicitly.
  22. group_printers = Table(
  23. "group_printers",
  24. Base.metadata,
  25. Column("group_id", Integer, ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True),
  26. Column("printer_id", Integer, ForeignKey("printers.id", ondelete="CASCADE"), primary_key=True),
  27. )
  28. class Group(Base):
  29. """Group model for organizing users and assigning permissions.
  30. Groups contain a list of permissions that are granted to all members.
  31. Users can belong to multiple groups, and their permissions are additive.
  32. System groups (Administrators, Operators, Viewers) cannot be deleted.
  33. Permissions say *what* a member may do; ``restrict_printers`` says *where*.
  34. A group without the flag doesn't narrow printer access at all, so it can be
  35. combined with a team group that does. See ``core/printer_scope.py``.
  36. """
  37. __tablename__ = "groups"
  38. id: Mapped[int] = mapped_column(primary_key=True)
  39. name: Mapped[str] = mapped_column(String(100), unique=True, index=True)
  40. description: Mapped[str | None] = mapped_column(String(500), nullable=True)
  41. permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
  42. is_system: Mapped[bool] = mapped_column(Boolean, default=False)
  43. restrict_printers: Mapped[bool] = mapped_column(Boolean, default=False, server_default="0")
  44. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  45. updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
  46. # Relationship to users through association table
  47. users: Mapped[list[User]] = relationship(
  48. "User",
  49. secondary=user_groups,
  50. back_populates="groups",
  51. lazy="selectin",
  52. )
  53. def __repr__(self) -> str:
  54. return f"<Group {self.name}>"