44# --- BEGIN_HEADER ---
55#
66# logout - force-expire local login session(s)
7- # Copyright (C) 2003-2023 The MiG Project lead by Brian Vinter
7+ # Copyright (C) 2003-2025 The MiG Project lead by Brian Vinter
88#
99# This file is part of MiG.
1010#
4343 require_twofactor_setup , build_logout_url , extract_client_openid
4444from mig .shared .init import initialize_main_variables , find_entry
4545from mig .shared .useradm import expire_oid_sessions , find_oid_sessions
46+ from mig .shared .url import urlencode
4647
4748
4849def signature ():
@@ -115,13 +116,25 @@ def main(client_id, user_arguments_dict, environ=None):
115116 if i in ['setup' , 'logout' ]]
116117 title_entry ['user_menu' ] = []
117118
118- # OpenID requires logout on provider and in local mod-auth-openid database.
119+ # NOTE: actual logout is done in reverse order of login. Namely, start with
120+ # any gdp project logout, then 2FA session removal and finally complete any
121+ # IDP logout.
122+
123+ # NOTE: OpenID 2.0 requires logout on provider and in local mod-auth-openid
124+ # database.
119125 # IMPORTANT: some browsers like Firefox may inadvertently renew the local
120126 # OpenID session while loading the resources for this page (in parallel).
121127 # User clicks logout at OpenID provider, which sends her back here to
122128 # finish the local logout.
129+ # NOTE: OpenID Connect on the other hand cannot return after logout and
130+ # must complete gdp and 2FA session clean up before calling remote logout.
123131
124132 if do_logout :
133+ if configuration .site_enable_gdp :
134+ logger .debug ("close any open GDP project for %s" % client_id )
135+ project_logout (configuration , 'https' , environ ['REMOTE_ADDR' ],
136+ client_id )
137+
125138 if configuration .site_enable_twofactor :
126139 real_user = client_id
127140 addr_filter = environ ['REMOTE_ADDR' ]
@@ -131,6 +144,7 @@ def main(client_id, user_arguments_dict, environ=None):
131144 real_user = get_client_id_from_project_client_id (configuration ,
132145 client_id )
133146 addr_filter = None
147+ logger .debug ("expire twofactor session for %s" % client_id )
134148 if real_user and not expire_twofactor_session (configuration ,
135149 real_user , environ ,
136150 allow_missing = True ,
@@ -144,7 +158,28 @@ def main(client_id, user_arguments_dict, environ=None):
144158the %s Admins if it happens repeatedly.
145159</p>""" % configuration .short_title
146160 })
161+
162+ if auth_type == AUTH_OPENID_V2 :
163+ if auth_flavor == AUTH_MIG_OID :
164+ reentry_page = configuration .migserver_https_mig_oid_url
165+ elif auth_flavor == AUTH_EXT_OID :
166+ reentry_page = configuration .migserver_https_ext_oid_url
167+ elif auth_type == AUTH_OPENID_CONNECT :
168+ if auth_flavor == AUTH_MIG_OIDC :
169+ reentry_page = configuration .migserver_https_mig_oidc_url
170+ elif auth_flavor == AUTH_EXT_OIDC :
171+ reentry_page = configuration .migserver_https_ext_oidc_url
172+ elif auth_type == AUTH_CERTIFICATE :
173+ if auth_flavor == AUTH_MIG_CERT :
174+ reentry_page = configuration .migserver_https_mig_cert_url
175+ elif auth_flavor == AUTH_EXT_CERT :
176+ reentry_page = configuration .migserver_https_ext_cert_url
177+ else :
178+ reentry_page = "https://%s" % configuration .server_fqdn
179+
147180 if auth_type == AUTH_OPENID_V2 :
181+ # TODO: truncate open_id_session_id instead as suggested in the FAQ?
182+ # https://findingscience.com/mod_auth_openid/faq.html
148183 (oid_db , identity ) = extract_client_openid (configuration , environ ,
149184 lookup_dn = False )
150185 logger .info ("expiring active sessions for %s in %s" % (identity ,
@@ -155,6 +190,18 @@ def main(client_id, user_arguments_dict, environ=None):
155190 configuration , oid_db , identity )
156191 elif auth_type == AUTH_OPENID_CONNECT :
157192 # NOTE: mod auth oidc handles local logout automatically
193+ logger .debug ("pass through local %s auth module for %s session end"
194+ % (auth_type , identity ))
195+ # NOTE: OpenID Connect module handles remote logout on vanity url
196+ terminate_session_url = '/dynamic/redirect_uri'
197+ # NOTE: upstream currently requires fixed logout callback url
198+ # TODO: change return_page to reentry_page when upstream allows it
199+ return_page = build_logout_url (configuration , environ )
200+ terminate_query = urlencode ({'logout' : return_page })
201+ reentry_page = terminate_session_url + '?%s' % terminate_query
202+ logger .debug ("send %s to %s for logout" % (client_id , reentry_page ))
203+ success , found , remaining = True , True , []
204+ elif auth_type == AUTH_CERTIFICATE :
158205 logger .info ("no manual cleanup needed for %s session of %s" %
159206 (auth_type , identity ))
160207 success , found , remaining = True , True , []
@@ -170,34 +217,13 @@ def main(client_id, user_arguments_dict, environ=None):
170217 return (output_objects , status )
171218
172219 if success and found and not remaining :
173- if configuration .site_enable_gdp :
174- reentry_page = "https://%s" % configuration .server_fqdn
175- if auth_type == AUTH_OPENID_V2 :
176- if auth_flavor == AUTH_MIG_OID :
177- reentry_page = configuration .migserver_https_mig_oid_url
178- elif auth_flavor == AUTH_EXT_OID :
179- reentry_page = configuration .migserver_https_ext_oid_url
180- elif auth_type == AUTH_OPENID_CONNECT :
181- if auth_flavor == AUTH_MIG_OIDC :
182- reentry_page = configuration .migserver_https_mig_oidc_url
183- elif auth_flavor == AUTH_EXT_OIDC :
184- reentry_page = configuration .migserver_https_ext_oidc_url
185- project_logout (
186- configuration , 'https' , environ ['REMOTE_ADDR' ], client_id )
187- html = """
188- <a id='gdp_logout' href='%s'></a>
220+ # TODO: switch to 'window.location = reentry_page' ?
221+ html = """
222+ <a id='finish_logout' href='%s'></a>
189223 <script type='text/javascript'>
190- document.getElementById('gdp_logout ').click();
224+ document.getElementById('finish_logout ').click();
191225 </script>""" % reentry_page
192- output_objects .append (
193- {'object_type' : 'html_form' , 'text' : html })
194- else :
195- output_objects .append (
196- {'object_type' : 'text' , 'text' :
197- """You are now logged out of %s locally - you may want to
198- close your web browser to finish""" % configuration .short_title })
199- title_entry ['skipmenu' ] = True
200-
226+ output_objects .append ({'object_type' : 'html_form' , 'text' : html })
201227 else :
202228 logger .error ("remaining active sessions for %s: %s" % (identity ,
203229 remaining ))
@@ -218,6 +244,7 @@ def main(client_id, user_arguments_dict, environ=None):
218244 {'object_type' : 'link' ,
219245 'destination' : 'javascript:history.back();' ,
220246 'class' : 'genericbutton greenBtn' , 'text' : "No, go back" })
247+
221248 # sub-container end
222249 output_objects .append ({'object_type' : 'html_form' , 'text' : '''
223250 </div>
0 commit comments