mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #9488 from almahmoud/idp-logout
[20.01] Backporting PR #9162 into 20.01 branch
This commit is contained in:
@@ -14,12 +14,26 @@ function logoutClick() {
|
||||
const galaxy = getGalaxyInstance();
|
||||
const session_csrf_token = galaxy.session_csrf_token;
|
||||
const url = `${galaxy.root}user/logout?session_csrf_token=${session_csrf_token}`;
|
||||
axios.get(url).then(() => {
|
||||
if (galaxy.user) {
|
||||
galaxy.user.clearSessionStorage();
|
||||
}
|
||||
window.top.location.href = `${galaxy.root}root/login?is_logout_redirect=true`;
|
||||
});
|
||||
axios
|
||||
.get(url)
|
||||
.then(() => {
|
||||
if (galaxy.user) {
|
||||
galaxy.user.clearSessionStorage();
|
||||
}
|
||||
// Check if we need to logout of OIDC IDP
|
||||
if (galaxy.config.enable_oidc) {
|
||||
return axios.get(`${galaxy.root}authnz/logout`);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data && response.data.redirect_uri) {
|
||||
window.top.location.href = response.data.redirect_uri;
|
||||
} else {
|
||||
window.top.location.href = `${galaxy.root}root/login?is_logout_redirect=true`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const Collection = Backbone.Collection.extend({
|
||||
|
||||
@@ -70,3 +70,16 @@ class IdentityProvider(object):
|
||||
|
||||
def disconnect(self, provider, trans, disconnect_redirect_url=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
def logout(self, trans, post_logout_redirect_url=None):
|
||||
"""
|
||||
Return a URL that will log the user out of the IDP. In OIDC this is
|
||||
called the 'end_session_endpoint'.
|
||||
|
||||
:type trans: GalaxyWebTransaction
|
||||
:param trans: Galaxy web transaction.
|
||||
|
||||
:type trans: string
|
||||
:param trans: Optional URL to redirect to after logging out of IDP.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -9,6 +9,7 @@ import jwt
|
||||
import requests
|
||||
from oauthlib.common import generate_nonce
|
||||
from requests_oauthlib import OAuth2Session
|
||||
from six.moves.urllib.parse import quote
|
||||
|
||||
from galaxy import util
|
||||
from galaxy.model import CustosAuthnzToken, User
|
||||
@@ -39,6 +40,7 @@ class CustosAuthnz(IdentityProvider):
|
||||
self.config['authorization_endpoint'] = well_known_oidc_config['authorization_endpoint']
|
||||
self.config['token_endpoint'] = well_known_oidc_config['token_endpoint']
|
||||
self.config['userinfo_endpoint'] = well_known_oidc_config['userinfo_endpoint']
|
||||
self.config['end_session_endpoint'] = well_known_oidc_config['end_session_endpoint']
|
||||
else:
|
||||
realm = oidc_backend_config['realm']
|
||||
self._load_config_for_provider_and_realm(self.config['provider'], realm)
|
||||
@@ -142,6 +144,16 @@ class CustosAuthnz(IdentityProvider):
|
||||
except Exception as e:
|
||||
return False, "Failed to disconnect provider {}: {}".format(provider, util.unicodify(e)), None
|
||||
|
||||
def logout(self, trans, post_logout_redirect_url=None):
|
||||
try:
|
||||
redirect_url = self.config['end_session_endpoint']
|
||||
if post_logout_redirect_url is not None:
|
||||
redirect_url += "?redirect_uri={}".format(quote(post_logout_redirect_url))
|
||||
return redirect_url
|
||||
except Exception as e:
|
||||
log.error("Failed to generate logout redirect_url", exc_info=e)
|
||||
return None
|
||||
|
||||
def _create_oauth2_session(self, state=None, scope=None):
|
||||
client_id = self.config['client_id']
|
||||
redirect_uri = self.config['redirect_uri']
|
||||
@@ -191,6 +203,7 @@ class CustosAuthnz(IdentityProvider):
|
||||
self.config['authorization_endpoint'] = well_known_oidc_config['authorization_endpoint']
|
||||
self.config['token_endpoint'] = well_known_oidc_config['token_endpoint']
|
||||
self.config['userinfo_endpoint'] = well_known_oidc_config['userinfo_endpoint']
|
||||
self.config['end_session_endpoint'] = well_known_oidc_config['end_session_endpoint']
|
||||
|
||||
def _get_well_known_uri_for_provider_and_realm(self, provider, realm):
|
||||
# TODO: Look up this URL from a Python library
|
||||
|
||||
@@ -16,7 +16,7 @@ from six.moves import builtins
|
||||
|
||||
from galaxy import exceptions
|
||||
from galaxy import model
|
||||
from galaxy.util import string_as_bool
|
||||
from galaxy.util import asbool, string_as_bool
|
||||
from galaxy.util import unicodify
|
||||
from .custos_authnz import CustosAuthnz
|
||||
from .psa_authnz import (
|
||||
@@ -117,7 +117,8 @@ class AuthnzManager(object):
|
||||
rtv = {
|
||||
'client_id': config_xml.find('client_id').text,
|
||||
'client_secret': config_xml.find('client_secret').text,
|
||||
'redirect_uri': config_xml.find('redirect_uri').text}
|
||||
'redirect_uri': config_xml.find('redirect_uri').text,
|
||||
'enable_idp_logout': asbool(config_xml.findtext('enable_idp_logout', 'false'))}
|
||||
if config_xml.find('prompt') is not None:
|
||||
rtv['prompt'] = config_xml.find('prompt').text
|
||||
return rtv
|
||||
@@ -128,7 +129,8 @@ class AuthnzManager(object):
|
||||
'client_id': config_xml.find('client_id').text,
|
||||
'client_secret': config_xml.find('client_secret').text,
|
||||
'redirect_uri': config_xml.find('redirect_uri').text,
|
||||
'realm': config_xml.find('realm').text}
|
||||
'realm': config_xml.find('realm').text,
|
||||
'enable_idp_logout': asbool(config_xml.findtext('enable_idp_logout', 'false'))}
|
||||
if config_xml.find('well_known_oidc_config_uri') is not None:
|
||||
rtv['well_known_oidc_config_uri'] = config_xml.find('well_known_oidc_config_uri').text
|
||||
if config_xml.find('idphint') is not None:
|
||||
@@ -262,6 +264,35 @@ class AuthnzManager(object):
|
||||
log.exception(msg)
|
||||
return False, msg, (None, None)
|
||||
|
||||
def logout(self, provider, trans, post_logout_redirect_url=None):
|
||||
"""
|
||||
Log the user out of the identity provider.
|
||||
|
||||
:type provider: string
|
||||
:param provider: set the name of the identity provider.
|
||||
:type trans: GalaxyWebTransaction
|
||||
:param trans: Galaxy web transaction.
|
||||
:type post_logout_redirect_url: string
|
||||
:param post_logout_redirect_url: (Optional) URL for identity provider
|
||||
to redirect to after logging user out.
|
||||
:return: a tuple (success boolean, message, redirect URI)
|
||||
"""
|
||||
try:
|
||||
# check if logout is enabled for this idp and return false if not
|
||||
unified_provider_name = self._unify_provider_name(provider)
|
||||
if self.oidc_backends_config[unified_provider_name]['enable_idp_logout'] is False:
|
||||
return False, "IDP logout is not enabled for {}".format(provider), None
|
||||
|
||||
success, message, backend = self._get_authnz_backend(provider)
|
||||
if success is False:
|
||||
return False, message, None
|
||||
return True, message, backend.logout(trans, post_logout_redirect_url)
|
||||
except Exception as e:
|
||||
msg = 'The following error occurred when logging out from `{}` identity provider: ' \
|
||||
'{}'.format(provider, e.message)
|
||||
log.exception(msg)
|
||||
return False, msg, None
|
||||
|
||||
def disconnect(self, provider, trans, disconnect_redirect_url=None):
|
||||
try:
|
||||
success, message, backend = self._get_authnz_backend(provider)
|
||||
|
||||
@@ -99,6 +99,8 @@ Please mind `http` and `https`.
|
||||
<!-- <well_known_oidc_config_uri>https://.../.well-known/openid-configuration</well_known_oidc_config_uri> -->
|
||||
<!-- (Optional) Override the default idp hint -->
|
||||
<!-- <idphint>cilogon</idphint> -->
|
||||
<!-- (Optional) Enable logging out of the IDP when user logs out of Galaxy -->
|
||||
<!-- <enable_idp_logout>true</enable_idp_logout> -->
|
||||
</provider>
|
||||
<!-- Documentation: https://galaxyproject.org/authnz/config/oidc/idps/elixir-aai -->
|
||||
<provider name="Elixir">
|
||||
|
||||
@@ -72,6 +72,9 @@ def app_factory(global_conf, load_app_kwds={}, **kwargs):
|
||||
webapp.add_route('/authnz/{provider}/login', controller='authnz', action='login', provider=None)
|
||||
webapp.add_route('/authnz/{provider}/callback', controller='authnz', action='callback', provider=None)
|
||||
webapp.add_route('/authnz/{provider}/disconnect', controller='authnz', action='disconnect', provider=None)
|
||||
webapp.add_route('/authnz/{provider}/logout', controller='authnz', action='logout', provider=None)
|
||||
# Returns the provider specific logout url for currently logged in provider
|
||||
webapp.add_route('/authnz/logout', controller='authnz', action='get_logout_url')
|
||||
|
||||
# These two routes handle our simple needs at the moment
|
||||
webapp.add_route('/async/{tool_id}/{data_id}/{data_secret}', controller='async', action='index', tool_id=None, data_id=None, data_secret=None)
|
||||
|
||||
@@ -13,6 +13,8 @@ from galaxy.webapps.base.controller import JSAppLauncher
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PROVIDER_COOKIE_NAME = 'oidc-provider'
|
||||
|
||||
|
||||
class OIDC(JSAppLauncher):
|
||||
|
||||
@@ -79,6 +81,8 @@ class OIDC(JSAppLauncher):
|
||||
"identity provider. Please try again, and if the problem persists, "
|
||||
"contact the Galaxy instance admin.".format(provider))
|
||||
trans.handle_user_login(user)
|
||||
# Record which idp provider was logged into, so we can logout of it later
|
||||
trans.set_cookie(value=provider, name=PROVIDER_COOKIE_NAME)
|
||||
return trans.response.send_redirect(url_for('/'))
|
||||
|
||||
@web.expose
|
||||
@@ -95,3 +99,20 @@ class OIDC(JSAppLauncher):
|
||||
if redirect_url is None:
|
||||
redirect_url = url_for('/')
|
||||
return trans.response.send_redirect(redirect_url)
|
||||
|
||||
@web.json
|
||||
def logout(self, trans, provider, **kwargs):
|
||||
post_logout_redirect_url = trans.request.base + url_for('/') + 'root/login?is_logout_redirect=true'
|
||||
success, message, redirect_uri = trans.app.authnz_manager.logout(provider,
|
||||
trans,
|
||||
post_logout_redirect_url=post_logout_redirect_url)
|
||||
if success:
|
||||
return {'redirect_uri': redirect_uri}
|
||||
else:
|
||||
return {'message': message}
|
||||
|
||||
@web.expose
|
||||
def get_logout_url(self, trans, **kwargs):
|
||||
idp_provider = trans.get_cookie(name=PROVIDER_COOKIE_NAME)
|
||||
if idp_provider:
|
||||
return trans.response.send_redirect(url_for(controller='authnz', action='logout', provider=idp_provider))
|
||||
|
||||
@@ -6,7 +6,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
import jwt
|
||||
import requests
|
||||
from six.moves.urllib.parse import parse_qs, urlparse
|
||||
from six.moves.urllib.parse import parse_qs, quote, urlparse
|
||||
|
||||
from galaxy.authnz import custos_authnz
|
||||
from galaxy.model import CustosAuthnzToken, User
|
||||
@@ -33,7 +33,8 @@ class CustosAuthnzTestCase(unittest.TestCase):
|
||||
requests.get = self.mockRequest(self._get_idp_url(), {
|
||||
"authorization_endpoint": "https://test-auth-endpoint",
|
||||
"token_endpoint": "https://test-token-endpoint",
|
||||
"userinfo_endpoint": "https://test-userinfo-endpoint"
|
||||
"userinfo_endpoint": "https://test-userinfo-endpoint",
|
||||
"end_session_endpoint": "https://test-end-session-endpoint"
|
||||
})
|
||||
self.custos_authnz = custos_authnz.CustosAuthnz('Custos', {
|
||||
'VERIFY_SSL': True
|
||||
@@ -476,3 +477,10 @@ class CustosAuthnzTestCase(unittest.TestCase):
|
||||
self.assertFalse(success)
|
||||
self.assertNotEqual("", message)
|
||||
self.assertIsNone(redirect_uri)
|
||||
|
||||
def test_logout_with_redirect(self):
|
||||
|
||||
logout_redirect_url = "http://localhost:8080/post-logout"
|
||||
redirect_url = self.custos_authnz.logout(self.trans, logout_redirect_url)
|
||||
|
||||
self.assertEqual(redirect_url, "https://test-end-session-endpoint?redirect_uri=" + quote(logout_redirect_url))
|
||||
|
||||
Reference in New Issue
Block a user