authentik.core.sessions
authentik sessions engine
1"""authentik sessions engine""" 2 3import pickle # nosec 4 5from django.contrib.auth import BACKEND_SESSION_KEY, HASH_SESSION_KEY, SESSION_KEY 6from django.contrib.sessions.backends.db import SessionStore as SessionBase 7from django.core.exceptions import SuspiciousOperation 8from django.utils import timezone 9from django.utils.functional import cached_property 10from structlog.stdlib import get_logger 11 12from authentik.root.middleware import ClientIPMiddleware 13 14LOGGER = get_logger() 15 16 17class SessionStore(SessionBase): 18 def __init__(self, session_key=None, last_ip=None, last_user_agent=""): 19 super().__init__(session_key) 20 self._create_kwargs = { 21 "last_ip": last_ip or ClientIPMiddleware.default_ip, 22 "last_user_agent": last_user_agent, 23 } 24 25 @classmethod 26 def get_model_class(cls): 27 from authentik.core.models import Session 28 29 return Session 30 31 @cached_property 32 def model_fields(self): 33 return [k.value for k in self.model.Keys] 34 35 def _get_session_from_db(self): 36 try: 37 return self.model.objects.select_related( 38 "authenticatedsession", 39 "authenticatedsession__user", 40 ).get( 41 session_key=self.session_key, 42 expires__gt=timezone.now(), 43 ) 44 except (self.model.DoesNotExist, SuspiciousOperation) as exc: 45 if isinstance(exc, SuspiciousOperation): 46 LOGGER.warning(str(exc)) 47 self._session_key = None 48 49 async def _aget_session_from_db(self): 50 try: 51 return await self.model.objects.select_related( 52 "authenticatedsession", 53 "authenticatedsession__user", 54 ).aget( 55 session_key=self.session_key, 56 expires__gt=timezone.now(), 57 ) 58 except (self.model.DoesNotExist, SuspiciousOperation) as exc: 59 if isinstance(exc, SuspiciousOperation): 60 LOGGER.warning(str(exc)) 61 self._session_key = None 62 63 def encode(self, session_dict): 64 return pickle.dumps(session_dict, protocol=pickle.HIGHEST_PROTOCOL) 65 66 def decode(self, session_data): 67 try: 68 return pickle.loads(session_data) # nosec 69 except pickle.PickleError, AttributeError, TypeError: 70 # PickleError, ValueError - unpickling exceptions 71 # AttributeError - can happen when Django model fields (e.g., FileField) are unpickled 72 # and their descriptors fail to initialize (e.g., missing storage) 73 # TypeError - can happen with incompatible pickled objects 74 # If any of these happen, just return an empty dictionary (an empty session) 75 pass 76 return {} 77 78 def load(self): 79 s = self._get_session_from_db() 80 if s: 81 return { 82 "authenticatedsession": getattr(s, "authenticatedsession", None), 83 **{k: getattr(s, k) for k in self.model_fields}, 84 **self.decode(s.session_data), 85 } 86 else: 87 return {} 88 89 async def aload(self): 90 s = await self._aget_session_from_db() 91 if s: 92 return { 93 "authenticatedsession": getattr(s, "authenticatedsession", None), 94 **{k: getattr(s, k) for k in self.model_fields}, 95 **self.decode(s.session_data), 96 } 97 else: 98 return {} 99 100 def create_model_instance(self, data): 101 args = { 102 "session_key": self._get_or_create_session_key(), 103 "expires": self.get_expiry_date(), 104 "session_data": {}, 105 **self._create_kwargs, 106 } 107 for k, v in data.items(): 108 # Don't save: 109 # - unused auth data 110 # - related models 111 if k in [SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY, "authenticatedsession"]: 112 pass 113 elif k in self.model_fields: 114 args[k] = v 115 else: 116 args["session_data"][k] = v 117 args["session_data"] = self.encode(args["session_data"]) 118 return self.model(**args) 119 120 async def acreate_model_instance(self, data): 121 args = { 122 "session_key": await self._aget_or_create_session_key(), 123 "expires": await self.aget_expiry_date(), 124 "session_data": {}, 125 **self._create_kwargs, 126 } 127 for k, v in data.items(): 128 # Don't save: 129 # - unused auth data 130 # - related models 131 if k in [SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY, "authenticatedsession"]: 132 pass 133 elif k in self.model_fields: 134 args[k] = v 135 else: 136 args["session_data"][k] = v 137 args["session_data"] = self.encode(args["session_data"]) 138 return self.model(**args) 139 140 @classmethod 141 def clear_expired(cls): 142 cls.get_model_class().objects.filter(expires__lt=timezone.now()).delete() 143 144 @classmethod 145 async def aclear_expired(cls): 146 await cls.get_model_class().objects.filter(expires__lt=timezone.now()).adelete() 147 148 def cycle_key(self): 149 data = self._session 150 key = self.session_key 151 self.create() 152 self._session_cache = data 153 if key: 154 self.delete(key) 155 if (authenticated_session := data.get("authenticatedsession")) is not None: 156 authenticated_session.session_id = self.session_key 157 authenticated_session.save(force_insert=True)
LOGGER =
<BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
class
SessionStore(django.contrib.sessions.backends.db.SessionStore):
18class SessionStore(SessionBase): 19 def __init__(self, session_key=None, last_ip=None, last_user_agent=""): 20 super().__init__(session_key) 21 self._create_kwargs = { 22 "last_ip": last_ip or ClientIPMiddleware.default_ip, 23 "last_user_agent": last_user_agent, 24 } 25 26 @classmethod 27 def get_model_class(cls): 28 from authentik.core.models import Session 29 30 return Session 31 32 @cached_property 33 def model_fields(self): 34 return [k.value for k in self.model.Keys] 35 36 def _get_session_from_db(self): 37 try: 38 return self.model.objects.select_related( 39 "authenticatedsession", 40 "authenticatedsession__user", 41 ).get( 42 session_key=self.session_key, 43 expires__gt=timezone.now(), 44 ) 45 except (self.model.DoesNotExist, SuspiciousOperation) as exc: 46 if isinstance(exc, SuspiciousOperation): 47 LOGGER.warning(str(exc)) 48 self._session_key = None 49 50 async def _aget_session_from_db(self): 51 try: 52 return await self.model.objects.select_related( 53 "authenticatedsession", 54 "authenticatedsession__user", 55 ).aget( 56 session_key=self.session_key, 57 expires__gt=timezone.now(), 58 ) 59 except (self.model.DoesNotExist, SuspiciousOperation) as exc: 60 if isinstance(exc, SuspiciousOperation): 61 LOGGER.warning(str(exc)) 62 self._session_key = None 63 64 def encode(self, session_dict): 65 return pickle.dumps(session_dict, protocol=pickle.HIGHEST_PROTOCOL) 66 67 def decode(self, session_data): 68 try: 69 return pickle.loads(session_data) # nosec 70 except pickle.PickleError, AttributeError, TypeError: 71 # PickleError, ValueError - unpickling exceptions 72 # AttributeError - can happen when Django model fields (e.g., FileField) are unpickled 73 # and their descriptors fail to initialize (e.g., missing storage) 74 # TypeError - can happen with incompatible pickled objects 75 # If any of these happen, just return an empty dictionary (an empty session) 76 pass 77 return {} 78 79 def load(self): 80 s = self._get_session_from_db() 81 if s: 82 return { 83 "authenticatedsession": getattr(s, "authenticatedsession", None), 84 **{k: getattr(s, k) for k in self.model_fields}, 85 **self.decode(s.session_data), 86 } 87 else: 88 return {} 89 90 async def aload(self): 91 s = await self._aget_session_from_db() 92 if s: 93 return { 94 "authenticatedsession": getattr(s, "authenticatedsession", None), 95 **{k: getattr(s, k) for k in self.model_fields}, 96 **self.decode(s.session_data), 97 } 98 else: 99 return {} 100 101 def create_model_instance(self, data): 102 args = { 103 "session_key": self._get_or_create_session_key(), 104 "expires": self.get_expiry_date(), 105 "session_data": {}, 106 **self._create_kwargs, 107 } 108 for k, v in data.items(): 109 # Don't save: 110 # - unused auth data 111 # - related models 112 if k in [SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY, "authenticatedsession"]: 113 pass 114 elif k in self.model_fields: 115 args[k] = v 116 else: 117 args["session_data"][k] = v 118 args["session_data"] = self.encode(args["session_data"]) 119 return self.model(**args) 120 121 async def acreate_model_instance(self, data): 122 args = { 123 "session_key": await self._aget_or_create_session_key(), 124 "expires": await self.aget_expiry_date(), 125 "session_data": {}, 126 **self._create_kwargs, 127 } 128 for k, v in data.items(): 129 # Don't save: 130 # - unused auth data 131 # - related models 132 if k in [SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY, "authenticatedsession"]: 133 pass 134 elif k in self.model_fields: 135 args[k] = v 136 else: 137 args["session_data"][k] = v 138 args["session_data"] = self.encode(args["session_data"]) 139 return self.model(**args) 140 141 @classmethod 142 def clear_expired(cls): 143 cls.get_model_class().objects.filter(expires__lt=timezone.now()).delete() 144 145 @classmethod 146 async def aclear_expired(cls): 147 await cls.get_model_class().objects.filter(expires__lt=timezone.now()).adelete() 148 149 def cycle_key(self): 150 data = self._session 151 key = self.session_key 152 self.create() 153 self._session_cache = data 154 if key: 155 self.delete(key) 156 if (authenticated_session := data.get("authenticatedsession")) is not None: 157 authenticated_session.session_id = self.session_key 158 authenticated_session.save(force_insert=True)
Implement database session store.
def
encode(self, session_dict):
64 def encode(self, session_dict): 65 return pickle.dumps(session_dict, protocol=pickle.HIGHEST_PROTOCOL)
Return the given session dictionary serialized and encoded as a string.
def
decode(self, session_data):
67 def decode(self, session_data): 68 try: 69 return pickle.loads(session_data) # nosec 70 except pickle.PickleError, AttributeError, TypeError: 71 # PickleError, ValueError - unpickling exceptions 72 # AttributeError - can happen when Django model fields (e.g., FileField) are unpickled 73 # and their descriptors fail to initialize (e.g., missing storage) 74 # TypeError - can happen with incompatible pickled objects 75 # If any of these happen, just return an empty dictionary (an empty session) 76 pass 77 return {}
def
load(self):
79 def load(self): 80 s = self._get_session_from_db() 81 if s: 82 return { 83 "authenticatedsession": getattr(s, "authenticatedsession", None), 84 **{k: getattr(s, k) for k in self.model_fields}, 85 **self.decode(s.session_data), 86 } 87 else: 88 return {}
Load the session data and return a dictionary.
def
create_model_instance(self, data):
101 def create_model_instance(self, data): 102 args = { 103 "session_key": self._get_or_create_session_key(), 104 "expires": self.get_expiry_date(), 105 "session_data": {}, 106 **self._create_kwargs, 107 } 108 for k, v in data.items(): 109 # Don't save: 110 # - unused auth data 111 # - related models 112 if k in [SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY, "authenticatedsession"]: 113 pass 114 elif k in self.model_fields: 115 args[k] = v 116 else: 117 args["session_data"][k] = v 118 args["session_data"] = self.encode(args["session_data"]) 119 return self.model(**args)
Return a new instance of the session model object, which represents the current session state. Intended to be used for saving the session data to the database.
async def
acreate_model_instance(self, data):
121 async def acreate_model_instance(self, data): 122 args = { 123 "session_key": await self._aget_or_create_session_key(), 124 "expires": await self.aget_expiry_date(), 125 "session_data": {}, 126 **self._create_kwargs, 127 } 128 for k, v in data.items(): 129 # Don't save: 130 # - unused auth data 131 # - related models 132 if k in [SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY, "authenticatedsession"]: 133 pass 134 elif k in self.model_fields: 135 args[k] = v 136 else: 137 args["session_data"][k] = v 138 args["session_data"] = self.encode(args["session_data"]) 139 return self.model(**args)
See create_model_instance().
@classmethod
def
clear_expired(cls):
141 @classmethod 142 def clear_expired(cls): 143 cls.get_model_class().objects.filter(expires__lt=timezone.now()).delete()
Remove expired sessions from the session store.
If this operation isn't possible on a given backend, it should raise NotImplementedError. If it isn't necessary, because the backend has a built-in expiration mechanism, it should be a no-op.
def
cycle_key(self):
149 def cycle_key(self): 150 data = self._session 151 key = self.session_key 152 self.create() 153 self._session_cache = data 154 if key: 155 self.delete(key) 156 if (authenticated_session := data.get("authenticatedsession")) is not None: 157 authenticated_session.session_id = self.session_key 158 authenticated_session.save(force_insert=True)
Create a new session key, while retaining the current session data.