Coverage for rfpy/model/acl.py: 95%
74 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 10:52 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 10:52 +0000
1import logging
2from typing import TYPE_CHECKING, Optional
4from sqlalchemy import (
5 DateTime,
6 text,
7 Integer,
8 ForeignKey,
9 UniqueConstraint,
10 TIMESTAMP,
11)
12from sqlalchemy.dialects import mysql
13from sqlalchemy.orm import (
14 relationship,
15 backref,
16 foreign,
17 Mapped,
18 mapped_column,
19 DynamicMapped,
20)
22from .meta import Base
23from rfpy.auth import ROLES
24from rfpy.model.humans import CustomRole
26if TYPE_CHECKING:
27 from rfpy.model.humans import User
28 from rfpy.model.project import Project
29 from rfpy.model.issue import Issue
30 from rfpy.model.humans import Organisation
31 from rfpy.model.questionnaire import Section
34log = logging.getLogger(__name__)
37class Participant(Base):
38 __tablename__ = "project_org_roles"
39 __table_args__ = (UniqueConstraint("project_id", "org_id"),) + Base.__table_args__
41 project_id: Mapped[int] = mapped_column(
42 mysql.INTEGER(11), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False
43 )
45 org_id: Mapped[str] = mapped_column(
46 mysql.VARCHAR(length=50),
47 ForeignKey("organisations.id", onupdate="CASCADE"),
48 nullable=False,
49 server_default=text("''"),
50 )
51 role: Mapped[Optional[str]] = mapped_column(mysql.VARCHAR(length=255), default=None, nullable=True)
52 organisation: Mapped["Organisation"] = relationship(
53 "Organisation", innerjoin=True, backref="participants"
54 )
55 project: Mapped["Project"] = relationship(
56 "Project", back_populates="participants_query"
57 )
59 permissions: DynamicMapped["ProjectPermission"] = relationship(
60 "ProjectPermission",
61 lazy="dynamic",
62 back_populates="participant",
63 cascade="all, delete",
64 passive_deletes=True,
65 )
66 custom_role: Mapped[CustomRole] = relationship(
67 CustomRole, foreign_keys=[role], primaryjoin=CustomRole.id == foreign(role)
68 )
70 def __repr__(self) -> str:
71 args = (self.org_id, self.project_id, self.role)
72 return "%s participant in project %s with role %s" % args
74 @property
75 def role_permissions(self):
76 return ROLES[self.role]
78 @property
79 def role_name(self):
80 if self.role in ROLES:
81 return self.role
82 elif self.custom_role:
83 return self.custom_role.name
84 else:
85 logging.error("Participant # %s has no role configured", self.id)
87 def as_dict(self) -> dict:
88 org_dict = self.organisation.as_dict()
89 return {"organisation": org_dict, "role": self.role_name}
92class ProjectPermission(Base):
93 __tablename__ = "project_permissions"
94 __table_args__ = (
95 UniqueConstraint("project_org_role_id", "user_id"),
96 ) + Base.__table_args__
98 project_org_role_id: Mapped[Optional[int]] = mapped_column(
99 mysql.INTEGER(11), ForeignKey("project_org_roles.id", ondelete="CASCADE"), nullable=True
100 )
102 user_id: Mapped[Optional[str]] = mapped_column(
103 mysql.VARCHAR(length=50), ForeignKey("users.id", ondelete="CASCADE"), nullable=True
104 )
106 user: Mapped["User"] = relationship("User", back_populates="project_permissions")
108 participant: Mapped["Participant"] = relationship(
109 "Participant", back_populates="permissions"
110 )
111 project: Mapped["Project"] = relationship(
112 "Project",
113 secondary="project_org_roles",
114 viewonly=True,
115 backref=backref("permissions", lazy="dynamic"),
116 )
118 def __repr__(self) -> str:
119 return "Permission for user %s on project_org_role %s" % (
120 self.user_id,
121 self.project_org_role_id,
122 )
125class SectionPermission(Base):
126 __tablename__ = "section_permissions"
127 __table_args__ = (
128 UniqueConstraint("project_permissions_id", "user_id", "section_id"),
129 ) + Base.__table_args__
131 section_id: Mapped[Optional[int]] = mapped_column(
132 mysql.INTEGER(11), ForeignKey("sections.id", ondelete="CASCADE"), nullable=True
133 )
134 user_id: Mapped[Optional[str]] = mapped_column(
135 mysql.VARCHAR(length=50), ForeignKey("users.id", ondelete="CASCADE"), nullable=True
136 )
137 project_permissions_id: Mapped[int] = mapped_column(
138 mysql.INTEGER(11),
139 ForeignKey("project_permissions.id", ondelete="CASCADE"),
140 nullable=False,
141 )
143 section: Mapped["Section"] = relationship(
144 "Section", backref=backref("permissions"), viewonly=True
145 )
146 user: Mapped["User"] = relationship("User", back_populates="section_permissions")
147 project_permission: Mapped["ProjectPermission"] = relationship(
148 "ProjectPermission", backref=backref("section_permissions")
149 )
151 def __repr__(self) -> str:
152 return "Permission for user %s on section %s" % (self.user_id, self.section_id)
155class TokenExpired(Exception):
156 pass
159class UserAdminToken(Base):
160 __tablename__ = "user_admin_tokens"
161 __mapper_args__ = {"polymorphic_on": "kind", "polymorphic_identity": None}
162 id = None # type: ignore
163 token: Mapped[str] = mapped_column(mysql.VARCHAR(length=255), primary_key=True)
164 user_id: Mapped[Optional[str]] = mapped_column(
165 mysql.VARCHAR(length=50), ForeignKey("users.id", ondelete="CASCADE")
166 )
167 email_address: Mapped[Optional[str]] = mapped_column(mysql.VARCHAR(length=100))
168 buyer_org_id: Mapped[Optional[str]] = mapped_column("buyer_org_id", mysql.VARCHAR(length=100))
169 manager_id: Mapped[Optional[str]] = mapped_column(mysql.VARCHAR(length=50))
170 issue_id: Mapped[Optional[int]] = mapped_column(
171 Integer, ForeignKey("issues.id", ondelete="CASCADE")
172 )
173 kind: Mapped[str] = mapped_column(mysql.VARCHAR(length=50), nullable=False)
174 created_date: Mapped[DateTime] = mapped_column(
175 TIMESTAMP, nullable=False, server_default=text("CURRENT_TIMESTAMP")
176 )
177 expiry_date: Mapped[DateTime] = mapped_column(DateTime, nullable=False)
179 user: Mapped["User"] = relationship("User", back_populates="tokens")
180 issue: Mapped["Issue"] = relationship("Issue")
182 def __repr__(self) -> str:
183 kind = self.kind if self.kind else "Undefined"
184 return "%s Token expires: %s" % (kind, self.expiry_date)
187class LoginToken(UserAdminToken):
188 __mapper_args__ = {"polymorphic_identity": "COLLAB_LOGIN"}
190 def __repr__(self) -> str:
191 return "Login Token for user: %s, expires: %s" % (
192 self.user_id,
193 self.expiry_date,
194 )
197class UserRegistrationToken(UserAdminToken):
198 __mapper_args__ = {"polymorphic_identity": "USER_REGISTRATION"}
200 def __repr__(self) -> str:
201 return "User Registration for %s, expires: %s" % (
202 self.email_address,
203 self.expiry_date,
204 )
207class IssueRegistrationToken(UserAdminToken):
208 __mapper_args__ = {"polymorphic_identity": "ISSUE_REGISTRATION"}
210 def __repr__(self) -> str:
211 return "Issue Registration for %s, expires: %s" % (
212 self.email_address,
213 self.expiry_date,
214 )
217class PasswordResetToken(UserAdminToken):
218 __mapper_args__ = {"polymorphic_identity": "PASSWORD_RESET"}
220 def __repr__(self) -> str:
221 return "Password Reset for user: %s, expires: %s" % (
222 self.user_id,
223 self.expiry_date,
224 )