mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-25 00:12:08 +08:00
Merge remote-tracking branch 'upstream/release_20.05' into dev
This commit is contained in:
@@ -19,11 +19,13 @@ from ..authnz import IdentityProvider
|
||||
log = logging.getLogger(__name__)
|
||||
STATE_COOKIE_NAME = 'custos-state'
|
||||
NONCE_COOKIE_NAME = 'custos-nonce'
|
||||
KEYCLOAK_BACKENDS = {'custos', 'cilogon', 'keycloak'}
|
||||
|
||||
|
||||
class CustosAuthnz(IdentityProvider):
|
||||
def __init__(self, provider, oidc_config, oidc_backend_config, idphint=None):
|
||||
self.config = {'provider': provider.lower()}
|
||||
provider = provider.lower()
|
||||
self.config = {'provider': provider}
|
||||
self.config['verify_ssl'] = oidc_config['VERIFY_SSL']
|
||||
self.config['url'] = oidc_backend_config['url']
|
||||
self.config['client_id'] = oidc_backend_config['client_id']
|
||||
@@ -31,28 +33,19 @@ class CustosAuthnz(IdentityProvider):
|
||||
if oidc_backend_config.get('credential_url'):
|
||||
# Keycloak client secret used to get token for custos
|
||||
self.config['credential_url'] = oidc_backend_config['credential_url']
|
||||
self._get_custos_credentials()
|
||||
if provider == 'custos':
|
||||
self._get_custos_credentials()
|
||||
self.config['redirect_uri'] = oidc_backend_config['redirect_uri']
|
||||
self.config['ca_bundle'] = oidc_backend_config.get('ca_bundle', None)
|
||||
self.config['extra_params'] = {
|
||||
'kc_idp_hint': oidc_backend_config.get('idphint', 'oidc' if self.config['provider'] == 'custos' else 'cilogon')
|
||||
'kc_idp_hint': oidc_backend_config.get('idphint', 'oidc' if self.config['provider'] in ['custos', 'keycloak'] else 'cilogon')
|
||||
}
|
||||
# Either get OIDC config from well-known config URI or lookup known urls based on provider name and realm
|
||||
if 'well_known_oidc_config_uri' in oidc_backend_config:
|
||||
self.config['well_known_oidc_config_uri'] = oidc_backend_config['well_known_oidc_config_uri']
|
||||
well_known_oidc_config = self._load_well_known_oidc_config(
|
||||
self.config['well_known_oidc_config_uri'])
|
||||
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']
|
||||
elif provider == 'cilogon':
|
||||
if provider == 'cilogon':
|
||||
self._load_config_for_cilogon()
|
||||
elif provider == 'custos':
|
||||
self._load_config_for_custos()
|
||||
else:
|
||||
realm = oidc_backend_config['realm']
|
||||
self._load_config_for_provider_and_realm(self.config['provider'], realm)
|
||||
elif provider == 'keycloak':
|
||||
self._load_config_for_keycloak()
|
||||
|
||||
def authenticate(self, trans, idphint=None):
|
||||
base_authorize_url = self.config['authorization_endpoint']
|
||||
@@ -226,16 +219,19 @@ class CustosAuthnz(IdentityProvider):
|
||||
self.config['userinfo_endpoint'] = "https://cilogon.org/oauth2/userinfo"
|
||||
|
||||
def _load_config_for_custos(self):
|
||||
self.config['well_known_oidc_config_uri'] = self._get_well_known_uri_from_url(self.config['provider'])
|
||||
# Set custos endpoints
|
||||
clientIdAndSec = self.config['client_id'] + ":" + self.config['client_secret']
|
||||
eps = requests.get(self.config['url'],
|
||||
headers={"Authorization": "Basic %s" % util.unicodify(base64.b64encode(util.smart_str(clientIdAndSec)))},
|
||||
verify=False, params={'client_id': self.config['client_id']})
|
||||
endpoints = eps.json()
|
||||
self.config['authorization_endpoint'] = endpoints['authorization_endpoint']
|
||||
self.config['token_endpoint'] = endpoints['token_endpoint']
|
||||
self.config['userinfo_endpoint'] = endpoints['userinfo_endpoint']
|
||||
self.config['end_session_endpoint'] = endpoints['end_session_endpoint']
|
||||
well_known_oidc_config = eps.json()
|
||||
self._load_well_known_oidc_config(well_known_oidc_config)
|
||||
|
||||
def _load_config_for_keycloak(self):
|
||||
self.config['well_known_oidc_config_uri'] = self._get_well_known_uri_from_url(self.config['provider'])
|
||||
well_known_oidc_config = self._fetch_well_known_oidc_config(self.config['well_known_oidc_config_uri'])
|
||||
self._load_well_known_oidc_config(well_known_oidc_config)
|
||||
|
||||
def _get_custos_credentials(self):
|
||||
clientIdAndSec = self.config['client_id'] + ":" + self.config['client_secret']
|
||||
@@ -245,25 +241,17 @@ class CustosAuthnz(IdentityProvider):
|
||||
credentials = creds.json()
|
||||
self.config['iam_client_secret'] = credentials['iam_client_secret']
|
||||
|
||||
def _load_config_for_provider_and_realm(self, provider, realm):
|
||||
self.config['well_known_oidc_config_uri'] = self._get_well_known_uri_for_provider_and_realm(provider, realm)
|
||||
well_known_oidc_config = self._load_well_known_oidc_config(self.config['well_known_oidc_config_uri'])
|
||||
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):
|
||||
def _get_well_known_uri_from_url(self, provider):
|
||||
# TODO: Look up this URL from a Python library
|
||||
if provider == 'custos':
|
||||
if provider in ['custos', 'keycloak']:
|
||||
base_url = self.config["url"]
|
||||
# Remove potential trailing slash to avoid "//realms"
|
||||
base_url = base_url if base_url[-1] != "/" else base_url[:-1]
|
||||
return "{}/realms/{}/.well-known/openid-configuration".format(base_url, realm)
|
||||
return "{}/.well-known/openid-configuration".format(base_url)
|
||||
else:
|
||||
raise Exception("Unknown Custos provider name: {}".format(provider))
|
||||
|
||||
def _load_well_known_oidc_config(self, well_known_uri):
|
||||
def _fetch_well_known_oidc_config(self, well_known_uri):
|
||||
try:
|
||||
return requests.get(well_known_uri,
|
||||
verify=self._get_verify_param()).json()
|
||||
@@ -271,6 +259,12 @@ class CustosAuthnz(IdentityProvider):
|
||||
log.error("Failed to load well-known OIDC config URI: {}".format(well_known_uri))
|
||||
raise
|
||||
|
||||
def _load_well_known_oidc_config(self, well_known_oidc_config):
|
||||
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_verify_param(self):
|
||||
"""Return 'ca_bundle' if 'verify_ssl' is true and 'ca_bundle' is configured."""
|
||||
# in requests_oauthlib, the verify param can either be a boolean or a CA bundle path
|
||||
|
||||
@@ -21,7 +21,10 @@ from galaxy.util import (
|
||||
string_as_bool,
|
||||
unicodify,
|
||||
)
|
||||
from .custos_authnz import CustosAuthnz
|
||||
from .custos_authnz import (
|
||||
CustosAuthnz,
|
||||
KEYCLOAK_BACKENDS,
|
||||
)
|
||||
from .psa_authnz import (
|
||||
BACKENDS_NAME,
|
||||
on_the_fly_config,
|
||||
@@ -113,7 +116,7 @@ class AuthnzManager(object):
|
||||
self.oidc_backends_config[idp] = self._parse_idp_config(child)
|
||||
self.oidc_backends_implementation[idp] = 'psa'
|
||||
self.app.config.oidc[idp] = {'icon': self._get_idp_icon(idp)}
|
||||
elif idp == 'custos' or idp == 'cilogon':
|
||||
elif idp in KEYCLOAK_BACKENDS:
|
||||
self.oidc_backends_config[idp] = self._parse_custos_config(child)
|
||||
self.oidc_backends_implementation[idp] = 'custos'
|
||||
self.app.config.oidc[idp] = {'icon': self._get_idp_icon(idp)}
|
||||
@@ -149,12 +152,9 @@ 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,
|
||||
'enable_idp_logout': asbool(config_xml.findtext('enable_idp_logout', 'false'))}
|
||||
if config_xml.find('credential_url') is not None:
|
||||
rtv['credential_url'] = config_xml.find('credential_url').text
|
||||
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:
|
||||
rtv['idphint'] = config_xml.find('idphint').text
|
||||
if config_xml.find('ca_bundle') is not None:
|
||||
@@ -177,7 +177,7 @@ class AuthnzManager(object):
|
||||
provider = unified_provider_name
|
||||
identity_provider_class = self._get_identity_provider_class(self.oidc_backends_implementation[provider])
|
||||
try:
|
||||
if (provider == 'cilogon' or provider == 'custos'):
|
||||
if provider in KEYCLOAK_BACKENDS:
|
||||
return True, "", identity_provider_class(unified_provider_name, self.oidc_config, self.oidc_backends_config[unified_provider_name], idphint=idphint)
|
||||
else:
|
||||
return True, "", identity_provider_class(unified_provider_name, self.oidc_config, self.oidc_backends_config[unified_provider_name])
|
||||
@@ -273,7 +273,7 @@ class AuthnzManager(object):
|
||||
success, message, backend = self._get_authnz_backend(provider, idphint=idphint)
|
||||
if success is False:
|
||||
return False, message, None
|
||||
elif (provider == 'cilogon' or provider == 'custos'):
|
||||
elif provider in KEYCLOAK_BACKENDS:
|
||||
return True, "Redirecting to the `{}` identity provider for authentication".format(provider), backend.authenticate(trans, idphint)
|
||||
return True, "Redirecting to the `{}` identity provider for authentication".format(provider), backend.authenticate(trans)
|
||||
except Exception:
|
||||
|
||||
@@ -42,7 +42,7 @@ Some examples:
|
||||
- using localhost to authenticate with Google:
|
||||
http://localhost:8080/authnz/google/callback
|
||||
|
||||
- using localhost to authenticat with Globus:
|
||||
- using localhost to authenticate with Globus:
|
||||
http://localhost:8080/authnz/globus/callback
|
||||
|
||||
- using an instance hosted at `https://usegalaxy.org` with Google:
|
||||
@@ -90,8 +90,6 @@ Please mind `http` and `https`.
|
||||
<client_id> ... </client_id>
|
||||
<client_secret> ... </client_secret>
|
||||
<redirect_uri>http://localhost:8000/authnz/custos/callback</redirect_uri>
|
||||
|
||||
<realm> ... </realm>
|
||||
<!-- (Optional) Trusted CA certificate file or directory to use when verify Custos authorization server.
|
||||
See http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification for more information. -->
|
||||
<!-- <ca_bundle>/path/to/ca_bundle</ca_bundle> -->
|
||||
@@ -103,6 +101,25 @@ Please mind `http` and `https`.
|
||||
<!-- <enable_idp_logout>true</enable_idp_logout> -->
|
||||
<!-- <icon>https://path/to/icon</icon> -->
|
||||
</provider>
|
||||
|
||||
<provider name="Keycloak">
|
||||
<!-- The URL should include the base keycloak path as well as the realm -->
|
||||
<url>https://keycloak.example.org/realms/master/</url>
|
||||
<client_id> ... </client_id>
|
||||
<client_secret> ... </client_secret>
|
||||
<redirect_uri>http://localhost:8080/authnz/keycloak/callback</redirect_uri>
|
||||
<!-- (Optional) Trusted CA certificate file or directory to use when verify Custos authorization server.
|
||||
See http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification for more information. -->
|
||||
<!-- <ca_bundle>/path/to/ca_bundle</ca_bundle> -->
|
||||
<!-- (Optional) Override the default Custos well-known URL to point to a different instance -->
|
||||
<!-- <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> -->
|
||||
<!-- <icon>https://path/to/icon</icon> -->
|
||||
</provider>
|
||||
|
||||
<!-- Documentation: https://galaxyproject.org/authnz/config/oidc/idps/elixir-aai -->
|
||||
<provider name="Elixir">
|
||||
<client_id>...</client_id>
|
||||
@@ -133,4 +150,5 @@ Please mind `http` and `https`.
|
||||
-->
|
||||
<api_url> ... </api_url>
|
||||
</provider>
|
||||
|
||||
</OIDC>
|
||||
|
||||
@@ -20,14 +20,11 @@ class CustosAuthnzTestCase(unittest.TestCase):
|
||||
_get_userinfo_called = False
|
||||
_raw_token = None
|
||||
|
||||
def _get_base_idp_url(self):
|
||||
def _get_idp_url(self):
|
||||
# it would be ideal is we can use a URI as the following:
|
||||
# https://test_base_uri/auth
|
||||
return 'https://iam.scigap.org/auth'
|
||||
|
||||
def _get_idp_url(self):
|
||||
return "{}/realms/test-realm/.well-known/openid-configuration".format(self._get_base_idp_url())
|
||||
|
||||
def setUp(self):
|
||||
self.orig_requests_get = requests.get
|
||||
requests.get = self.mockRequest(self._get_idp_url(), {
|
||||
@@ -39,7 +36,7 @@ class CustosAuthnzTestCase(unittest.TestCase):
|
||||
self.custos_authnz = custos_authnz.CustosAuthnz('Custos', {
|
||||
'VERIFY_SSL': True
|
||||
}, {
|
||||
'url': self._get_base_idp_url(),
|
||||
'url': self._get_idp_url(),
|
||||
'client_id': 'test-client-id',
|
||||
'client_secret': 'test-client-secret',
|
||||
'redirect_uri': 'https://test-redirect-uri',
|
||||
@@ -203,7 +200,6 @@ class CustosAuthnzTestCase(unittest.TestCase):
|
||||
self.assertEqual(self.custos_authnz.config['client_id'], 'test-client-id')
|
||||
self.assertEqual(self.custos_authnz.config['client_secret'], 'test-client-secret')
|
||||
self.assertEqual(self.custos_authnz.config['redirect_uri'], 'https://test-redirect-uri')
|
||||
self.assertEqual(self.custos_authnz.config['well_known_oidc_config_uri'], self._get_idp_url())
|
||||
self.assertEqual(self.custos_authnz.config['authorization_endpoint'], 'https://test-auth-endpoint')
|
||||
self.assertEqual(self.custos_authnz.config['token_endpoint'], 'https://test-token-endpoint')
|
||||
self.assertEqual(self.custos_authnz.config['userinfo_endpoint'], 'https://test-userinfo-endpoint')
|
||||
@@ -236,7 +232,7 @@ class CustosAuthnzTestCase(unittest.TestCase):
|
||||
self.custos_authnz = custos_authnz.CustosAuthnz('Custos', {
|
||||
'VERIFY_SSL': True
|
||||
}, {
|
||||
'url': self._get_base_idp_url(),
|
||||
'url': self._get_idp_url(),
|
||||
'client_id': 'test-client-id',
|
||||
'client_secret': 'test-client-secret',
|
||||
'redirect_uri': 'http://localhost/auth/callback',
|
||||
|
||||
Reference in New Issue
Block a user