Coverage for src/mesh/model/user/user_interfaces.py: 93%
48 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +0000
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-28 07:45 +0000
1from dataclasses import asdict, dataclass, field
2from datetime import datetime
3from typing import ClassVar, Self
5from django.contrib.sessions.backends.base import SessionBase
7from mesh.models.user_models import User
10@dataclass
11class UserInfo:
12 pk: int
13 first_name: str
14 last_name: str
15 email: str
17 def __str__(self) -> str:
18 return f"{self.first_name} {self.last_name} ({self.email})"
20 @classmethod
21 def from_user(cls, user: User) -> Self:
22 return cls(
23 pk=user.pk,
24 first_name=user.first_name,
25 last_name=user.last_name,
26 email=user.email,
27 )
30@dataclass
31class ImpersonateData:
32 """
33 Interface for storing impersonation data.
34 """
36 _SESSION_KEY: ClassVar = "impersonate_data"
37 # Max session duration in seconds
38 _MAX_SESSION_DURATION: ClassVar = 10800 # 3 * 3600
39 source: UserInfo
40 target_id: int
41 # Timestamp in seconds (use default datetime.timestamp())s
42 timestamp_start: float | None = field(default=None)
43 target_role: str | None = field(default=None)
45 def __post_init__(self):
46 """
47 Dataclasses do not provide an effective fromdict method to deserialize
48 a dataclass (JSON to python dataclass object).
50 This enables to effectively deserialize a JSON into a ImpersonateData object,
51 by replacing the nested dict by their actual dataclass representation.
52 Beware this might not work well with typing (?)
53 """
54 source = self.source
55 if source and not isinstance(source, UserInfo):
56 self.source = UserInfo(**source)
58 if self.timestamp_start is None:
59 self.timestamp_start = datetime.utcnow().timestamp()
61 @classmethod
62 def from_session(cls, session: SessionBase) -> Self | None:
63 """
64 Returns the impersonate data from the given session.
65 """
66 data = session.get(cls._SESSION_KEY, None)
67 if data:
68 # In case the impersonate data is somehow corrupted.
69 try:
70 return cls(**data)
71 except Exception:
72 cls.clean_session(session)
73 return None
75 @classmethod
76 def clean_session(cls, session: SessionBase):
77 """
78 Discards the impersonate data in the given session.
79 """
80 if cls._SESSION_KEY in session: 80 ↛ exitline 80 didn't return from function 'clean_session' because the condition on line 80 was always true
81 del session[cls._SESSION_KEY]
82 session.save()
84 def serialize(self, session: SessionBase):
85 """
86 Serializes the ImpersonateData in the given session.
87 """
88 session[self._SESSION_KEY] = asdict(self)
90 def is_valid(self) -> bool:
91 """
92 Check if the impersonate data is still valid.
93 """
94 return (
95 datetime.utcnow().timestamp() < self.timestamp_start + self._MAX_SESSION_DURATION # type:ignore
96 )