22import re
33import uuid
44from functools import reduce
5+ from typing import Any
56from urllib .parse import parse_qs , urlencode , urlparse
67
78from django .conf import settings
89from django .contrib .auth import login , logout
910from django .contrib .sessions .models import Session as DjangoSession
1011from django .core .exceptions import PermissionDenied
12+ from django .db import transaction
1113from django .http .request import HttpRequest
1214from django .http .response import HttpResponse
1315from django .utils import timezone
16+ from django .utils .timezone import now
1417from shared .encryption .token import encode_token
1518from shared .license import LICENSE_ERRORS_MESSAGES , get_current_license
1619
@@ -59,7 +62,7 @@ class StateMixin(object):
5962
6063 """
6164
62- def __init__ (self , * args , ** kwargs ) :
65+ def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
6366 self .redis = get_redis_connection ()
6467 super ().__init__ (* args , ** kwargs )
6568
@@ -69,7 +72,7 @@ def _session_key(self) -> str:
6972 def _get_key_redis (self , state : str ) -> str :
7073 return f"oauth-state-{ state } "
7174
72- def _is_matching_cors_domains (self , url_domain ) -> bool :
75+ def _is_matching_cors_domains (self , url_domain : str ) -> bool :
7376 # make sure the domain is part of the CORS so that's a safe domain to
7477 # redirect to.
7578 if url_domain in settings .CORS_ALLOWED_ORIGINS :
@@ -79,7 +82,7 @@ def _is_matching_cors_domains(self, url_domain) -> bool:
7982 return True
8083 return False
8184
82- def _is_valid_redirection (self , to ) -> bool :
85+ def _is_valid_redirection (self , to : str ) -> bool :
8386 # make sure the redirect url is from a domain we own
8487 try :
8588 url = urlparse (to )
@@ -115,11 +118,11 @@ def generate_state(self) -> str:
115118
116119 return state
117120
118- def verify_state (self , state ) -> bool :
121+ def verify_state (self , state : str ) -> bool :
119122 state_from_session = self .request .session .get (self ._session_key (), None )
120123 return state_from_session and state == state_from_session
121124
122- def get_redirection_url_from_state (self , state ) -> ( str , bool ) :
125+ def get_redirection_url_from_state (self , state : str ) -> tuple [ str , bool ] :
123126 cached_url = self .redis .get (self ._get_key_redis (state ))
124127
125128 if not cached_url :
@@ -149,7 +152,7 @@ def get_redirection_url_from_state(self, state) -> (str, bool):
149152 # Return the final redirect URL to complete the login.
150153 return (cached_url .decode ("utf-8" ), True )
151154
152- def remove_state (self , state , delay = 0 ) -> None :
155+ def remove_state (self , state : str , delay : int = 0 ) -> None :
153156 redirection_url , _ = self .get_redirection_url_from_state (state )
154157 if delay == 0 :
155158 self .redis .delete (self ._get_key_redis (state ))
@@ -182,15 +185,17 @@ def modify_redirection_url_based_on_default_user_org(
182185 url += f"/{ owner_profile .default_org .username } "
183186 return url
184187
185- def get_or_create_org (self , single_organization ) :
188+ def get_or_create_org (self , single_organization : dict ) -> Owner :
186189 owner , was_created = Owner .objects .get_or_create (
187190 service = self .service ,
188191 service_id = single_organization ["id" ],
189192 defaults = {"createstamp" : timezone .now ()},
190193 )
191194 return owner
192195
193- def login_owner (self , owner : Owner , request : HttpRequest , response : HttpResponse ):
196+ def login_owner (
197+ self , owner : Owner , request : HttpRequest , response : HttpResponse
198+ ) -> None :
194199 # if there's a currently authenticated user
195200 if request .user is not None and not request .user .is_anonymous :
196201 if owner .user is None :
@@ -253,9 +258,11 @@ def login_owner(self, owner: Owner, request: HttpRequest, response: HttpResponse
253258
254259 request .session ["current_owner_id" ] = owner .pk
255260 RefreshService ().trigger_refresh (owner .ownerid , owner .username )
261+
262+ self .delete_expired_sessions_and_django_sessions (owner )
256263 self .store_login_session (owner )
257264
258- def get_and_modify_owner (self , user_dict , request ) -> Owner :
265+ def get_and_modify_owner (self , user_dict : dict , request : HttpRequest ) -> Owner :
259266 user_orgs = user_dict ["orgs" ]
260267 formatted_orgs = [
261268 dict (username = org ["username" ], id = str (org ["id" ])) for org in user_orgs
@@ -298,7 +305,9 @@ def get_and_modify_owner(self, user_dict, request) -> Owner:
298305
299306 return owner
300307
301- def _check_enterprise_organizations_membership (self , user_dict , orgs ):
308+ def _check_enterprise_organizations_membership (
309+ self , user_dict : dict , orgs : list [dict ]
310+ ) -> None :
302311 """Checks if a user belongs to the restricted organizations (or teams if GitHub) allowed in settings."""
303312 if settings .IS_ENTERPRISE and get_config (self .service , "organizations" ):
304313 orgs_in_settings = set (get_config (self .service , "organizations" ))
@@ -315,7 +324,7 @@ def _check_enterprise_organizations_membership(self, user_dict, orgs):
315324 "You must be a member of an allowed team in your organization."
316325 )
317326
318- def _check_user_count_limitations (self , login_data ) :
327+ def _check_user_count_limitations (self , login_data : dict ) -> None :
319328 if not settings .IS_ENTERPRISE :
320329 return
321330 license = get_current_license ()
@@ -339,7 +348,7 @@ def _check_user_count_limitations(self, login_data):
339348 owners_with_activated_users = Owner .objects .exclude (
340349 plan_activated_users__len = 0
341350 ).exclude (plan_activated_users__isnull = True )
342- all_distinct_actiaved_users = reduce (
351+ all_distinct_actiaved_users : set [ str ] = reduce (
343352 lambda acc , curr : set (curr .plan_activated_users ) | acc ,
344353 owners_with_activated_users ,
345354 set (),
@@ -357,7 +366,9 @@ def _check_user_count_limitations(self, login_data):
357366 if users_on_service_count > license .number_allowed_users :
358367 raise PermissionDenied (LICENSE_ERRORS_MESSAGES ["users-exceeded" ])
359368
360- def _get_or_create_owner (self , user_dict , request ):
369+ def _get_or_create_owner (
370+ self , user_dict : dict , request : HttpRequest
371+ ) -> tuple [Owner , bool ]:
361372 fields_to_update = ["oauth_token" , "private_access" , "updatestamp" ]
362373 login_data = user_dict ["user" ]
363374 owner , was_created = Owner .objects .get_or_create (
@@ -403,7 +414,7 @@ def _get_utm_params(self, params: dict) -> dict:
403414 # remove None values from the dict
404415 return {k : v for k , v in filtered_params .items () if v is not None }
405416
406- def store_to_cookie_utm_tags (self , response ) -> None :
417+ def store_to_cookie_utm_tags (self , response : HttpResponse ) -> None :
407418 if not settings .IS_ENTERPRISE :
408419 data = urlencode (self ._get_utm_params (self .request .GET ))
409420 response .set_cookie (
@@ -423,7 +434,7 @@ def retrieve_marketing_tags_from_cookie(self) -> dict:
423434 else :
424435 return {}
425436
426- def store_login_session (self , owner : Owner ):
437+ def store_login_session (self , owner : Owner ) -> None :
427438 # Store user's login session info after logging in
428439 http_x_forwarded_for = self .request .META .get ("HTTP_X_FORWARDED_FOR" )
429440 if http_x_forwarded_for :
@@ -443,3 +454,26 @@ def store_login_session(self, owner: Owner):
443454 type = Session .SessionType .LOGIN ,
444455 owner = owner ,
445456 )
457+
458+ def delete_expired_sessions_and_django_sessions (self , owner : Owner ) -> None :
459+ """
460+ This function deletes expired login sessions for a given owner
461+ """
462+ with transaction .atomic ():
463+ # Get the primary keys of expired DjangoSessions for the given owner
464+ expired_sessions = Session .objects .filter (
465+ owner = owner ,
466+ type = "login" ,
467+ login_session__isnull = False ,
468+ login_session__expire_date__lt = now (),
469+ )
470+
471+ # Delete the rows in the Session table using sessionid
472+ Session .objects .filter (
473+ sessionid__in = [es .sessionid for es in expired_sessions ]
474+ ).delete ()
475+
476+ # Delete the rows in the DjangoSession table using the extracted keys
477+ DjangoSession .objects .filter (
478+ session_key__in = [es .login_session for es in expired_sessions ]
479+ ).delete ()
0 commit comments