Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/contrib/sessions/backends/base.py: 42%
180 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import logging
2import string
3from datetime import datetime, timedelta
5from django.conf import settings
6from django.core import signing
7from django.utils import timezone
8from django.utils.crypto import get_random_string
9from django.utils.module_loading import import_string
11# session_key should not be case sensitive because some backends can store it
12# on case insensitive file systems.
13VALID_KEY_CHARS = string.ascii_lowercase + string.digits
16class CreateError(Exception):
17 """
18 Used internally as a consistent exception type to catch from save (see the
19 docstring for SessionBase.save() for details).
20 """
22 pass
25class UpdateError(Exception):
26 """
27 Occurs if Django tries to update a session that was deleted.
28 """
30 pass
33class SessionBase:
34 """
35 Base class for all Session classes.
36 """
38 TEST_COOKIE_NAME = "testcookie"
39 TEST_COOKIE_VALUE = "worked"
41 __not_given = object()
43 def __init__(self, session_key=None):
44 self._session_key = session_key
45 self.accessed = False
46 self.modified = False
47 self.serializer = import_string(settings.SESSION_SERIALIZER)
49 def __contains__(self, key):
50 return key in self._session
52 def __getitem__(self, key):
53 return self._session[key]
55 def __setitem__(self, key, value):
56 self._session[key] = value
57 self.modified = True
59 def __delitem__(self, key):
60 del self._session[key]
61 self.modified = True
63 @property
64 def key_salt(self):
65 return "django.contrib.sessions." + self.__class__.__qualname__
67 def get(self, key, default=None):
68 return self._session.get(key, default)
70 def pop(self, key, default=__not_given):
71 self.modified = self.modified or key in self._session
72 args = () if default is self.__not_given else (default,)
73 return self._session.pop(key, *args)
75 def setdefault(self, key, value):
76 if key in self._session:
77 return self._session[key]
78 else:
79 self.modified = True
80 self._session[key] = value
81 return value
83 def set_test_cookie(self):
84 self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
86 def test_cookie_worked(self):
87 return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
89 def delete_test_cookie(self):
90 del self[self.TEST_COOKIE_NAME]
92 def encode(self, session_dict):
93 "Return the given session dictionary serialized and encoded as a string."
94 return signing.dumps(
95 session_dict,
96 salt=self.key_salt,
97 serializer=self.serializer,
98 compress=True,
99 )
101 def decode(self, session_data):
102 try:
103 return signing.loads(
104 session_data, salt=self.key_salt, serializer=self.serializer
105 )
106 except signing.BadSignature:
107 logger = logging.getLogger("django.security.SuspiciousSession")
108 logger.warning("Session data corrupted")
109 except Exception:
110 # ValueError, unpickling exceptions. If any of these happen, just
111 # return an empty dictionary (an empty session).
112 pass
113 return {}
115 def update(self, dict_):
116 self._session.update(dict_)
117 self.modified = True
119 def has_key(self, key):
120 return key in self._session
122 def keys(self):
123 return self._session.keys()
125 def values(self):
126 return self._session.values()
128 def items(self):
129 return self._session.items()
131 def clear(self):
132 # To avoid unnecessary persistent storage accesses, we set up the
133 # internals directly (loading data wastes time, since we are going to
134 # set it to an empty dict anyway).
135 self._session_cache = {}
136 self.accessed = True
137 self.modified = True
139 def is_empty(self):
140 "Return True when there is no session_key and the session is empty."
141 try:
142 return not self._session_key and not self._session_cache
143 except AttributeError:
144 return True
146 def _get_new_session_key(self):
147 "Return session key that isn't being used."
148 while True:
149 session_key = get_random_string(32, VALID_KEY_CHARS)
150 if not self.exists(session_key):
151 return session_key
153 def _get_or_create_session_key(self):
154 if self._session_key is None:
155 self._session_key = self._get_new_session_key()
156 return self._session_key
158 def _validate_session_key(self, key):
159 """
160 Key must be truthy and at least 8 characters long. 8 characters is an
161 arbitrary lower bound for some minimal key security.
162 """
163 return key and len(key) >= 8
165 def _get_session_key(self):
166 return self.__session_key
168 def _set_session_key(self, value):
169 """
170 Validate session key on assignment. Invalid values will set to None.
171 """
172 if self._validate_session_key(value): 172 ↛ 173line 172 didn't jump to line 173, because the condition on line 172 was never true
173 self.__session_key = value
174 else:
175 self.__session_key = None
177 session_key = property(_get_session_key)
178 _session_key = property(_get_session_key, _set_session_key)
180 def _get_session(self, no_load=False):
181 """
182 Lazily load session from storage (unless "no_load" is True, when only
183 an empty dict is stored) and store it in the current instance.
184 """
185 self.accessed = True
186 try:
187 return self._session_cache
188 except AttributeError:
189 if self.session_key is None or no_load: 189 ↛ 192line 189 didn't jump to line 192, because the condition on line 189 was never false
190 self._session_cache = {}
191 else:
192 self._session_cache = self.load()
193 return self._session_cache
195 _session = property(_get_session)
197 def get_session_cookie_age(self):
198 return settings.SESSION_COOKIE_AGE
200 def get_expiry_age(self, **kwargs):
201 """Get the number of seconds until the session expires.
203 Optionally, this function accepts `modification` and `expiry` keyword
204 arguments specifying the modification and expiry of the session.
205 """
206 try:
207 modification = kwargs["modification"]
208 except KeyError:
209 modification = timezone.now()
210 # Make the difference between "expiry=None passed in kwargs" and
211 # "expiry not passed in kwargs", in order to guarantee not to trigger
212 # self.load() when expiry is provided.
213 try:
214 expiry = kwargs["expiry"]
215 except KeyError:
216 expiry = self.get("_session_expiry")
218 if not expiry: # Checks both None and 0 cases
219 return self.get_session_cookie_age()
220 if not isinstance(expiry, datetime):
221 return expiry
222 delta = expiry - modification
223 return delta.days * 86400 + delta.seconds
225 def get_expiry_date(self, **kwargs):
226 """Get session the expiry date (as a datetime object).
228 Optionally, this function accepts `modification` and `expiry` keyword
229 arguments specifying the modification and expiry of the session.
230 """
231 try:
232 modification = kwargs["modification"]
233 except KeyError:
234 modification = timezone.now()
235 # Same comment as in get_expiry_age
236 try:
237 expiry = kwargs["expiry"]
238 except KeyError:
239 expiry = self.get("_session_expiry")
241 if isinstance(expiry, datetime):
242 return expiry
243 expiry = expiry or self.get_session_cookie_age()
244 return modification + timedelta(seconds=expiry)
246 def set_expiry(self, value):
247 """
248 Set a custom expiration for the session. ``value`` can be an integer,
249 a Python ``datetime`` or ``timedelta`` object or ``None``.
251 If ``value`` is an integer, the session will expire after that many
252 seconds of inactivity. If set to ``0`` then the session will expire on
253 browser close.
255 If ``value`` is a ``datetime`` or ``timedelta`` object, the session
256 will expire at that specific future time.
258 If ``value`` is ``None``, the session uses the global session expiry
259 policy.
260 """
261 if value is None:
262 # Remove any custom expiration for this session.
263 try:
264 del self["_session_expiry"]
265 except KeyError:
266 pass
267 return
268 if isinstance(value, timedelta):
269 value = timezone.now() + value
270 self["_session_expiry"] = value
272 def get_expire_at_browser_close(self):
273 """
274 Return ``True`` if the session is set to expire when the browser
275 closes, and ``False`` if there's an expiry date. Use
276 ``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry
277 date/age, if there is one.
278 """
279 if self.get("_session_expiry") is None:
280 return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
281 return self.get("_session_expiry") == 0
283 def flush(self):
284 """
285 Remove the current session data from the database and regenerate the
286 key.
287 """
288 self.clear()
289 self.delete()
290 self._session_key = None
292 def cycle_key(self):
293 """
294 Create a new session key, while retaining the current session data.
295 """
296 data = self._session
297 key = self.session_key
298 self.create()
299 self._session_cache = data
300 if key:
301 self.delete(key)
303 # Methods that child classes must implement.
305 def exists(self, session_key):
306 """
307 Return True if the given session_key already exists.
308 """
309 raise NotImplementedError(
310 "subclasses of SessionBase must provide an exists() method"
311 )
313 def create(self):
314 """
315 Create a new session instance. Guaranteed to create a new object with
316 a unique key and will have saved the result once (with empty data)
317 before the method returns.
318 """
319 raise NotImplementedError(
320 "subclasses of SessionBase must provide a create() method"
321 )
323 def save(self, must_create=False):
324 """
325 Save the session data. If 'must_create' is True, create a new session
326 object (or raise CreateError). Otherwise, only update an existing
327 object and don't create one (raise UpdateError if needed).
328 """
329 raise NotImplementedError(
330 "subclasses of SessionBase must provide a save() method"
331 )
333 def delete(self, session_key=None):
334 """
335 Delete the session data under this key. If the key is None, use the
336 current session key value.
337 """
338 raise NotImplementedError(
339 "subclasses of SessionBase must provide a delete() method"
340 )
342 def load(self):
343 """
344 Load the session data and return a dictionary.
345 """
346 raise NotImplementedError(
347 "subclasses of SessionBase must provide a load() method"
348 )
350 @classmethod
351 def clear_expired(cls):
352 """
353 Remove expired sessions from the session store.
355 If this operation isn't possible on a given backend, it should raise
356 NotImplementedError. If it isn't necessary, because the backend has
357 a built-in expiration mechanism, it should be a no-op.
358 """
359 raise NotImplementedError("This backend does not support clear_expired().")