Skip to content

Commit

Permalink
Merge pull request #5903 from VJalili/cloudauthz
Browse files Browse the repository at this point in the history
Tested this by creating an authorization and, after creating an appropriate role in the AWS IAM dashboard, uploading some data from a private S3 bucket - works as expected. Some useful requests are available in the (newly created) Postman team for the Galaxy project (ping me for an invite): https://galaxyproject.postman.co/
  • Loading branch information
afgane committed Sep 24, 2018
2 parents 1549aaa + cd57114 commit 5355fe9
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 67 deletions.
122 changes: 120 additions & 2 deletions lib/galaxy/authnz/managers.py
@@ -1,10 +1,25 @@

import copy
import importlib
import logging
import xml.etree.ElementTree as ET
from xml.etree.ElementTree import ParseError

from .psa_authnz import PSAAuthnz
import requests
from cloudauthz import CloudAuthz
from cloudauthz.exceptions import (
CloudAuthzBaseException
)

from galaxy import exceptions
from galaxy import model
from .psa_authnz import (
BACKENDS_NAME,
on_the_fly_config,
PSAAuthnz,
Storage,
Strategy
)

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -89,8 +104,15 @@ def _parse_google_config(self, config_xml):
rtv['prompt'] = config_xml.find('prompt').text
return rtv

def _unify_provider_name(self, provider):
if provider in self.oidc_backends_config:
return provider.lower()
for k, v in BACKENDS_NAME.iteritems():
if v == provider:
return k.lower()

def _get_authnz_backend(self, provider):
provider = provider.lower()
provider = self._unify_provider_name(provider)
if provider in self.oidc_backends_config:
try:
return True, "", PSAAuthnz(provider, self.oidc_config, self.oidc_backends_config[provider])
Expand All @@ -102,6 +124,68 @@ def _get_authnz_backend(self, provider):
log.debug(msg)
return False, msg, None

def _extend_cloudauthz_config(self, trans, cloudauthz):
config = copy.deepcopy(cloudauthz.config)
if cloudauthz.provider == "aws":
success, message, backend = self._get_authnz_backend(cloudauthz.authn.provider)
strategy = Strategy(trans, Storage, backend.config)
on_the_fly_config(trans)
try:
config['id_token'] = cloudauthz.authn.get_id_token(strategy)
except requests.exceptions.HTTPError as e:
msg = "Sign-out from Galaxy and remove its access from `{}`, then log back in using `{}` " \
"account.".format(self._unify_provider_name(cloudauthz.authn.provider), cloudauthz.authn.uid)
log.debug("Failed to get/refresh ID token for user with ID `{}` for assuming authz_id `{}`. "
"User may not have a refresh token. If the problem persists, set the `prompt` key to "
"`consent` in `oidc_backends_config.xml`, then restart Galaxy and ask user to: {}"
"Error Message: `{}`".format(trans.user.id, cloudauthz.id, msg, e.response.text))
raise exceptions.AuthenticationFailed(
err_msg="An error occurred getting your ID token. {}. If the problem persists, please "
"contact Galaxy admin.".format(msg))
return config

@staticmethod
def can_user_assume_authn(trans, authn_id):
qres = trans.sa_session.query(model.UserAuthnzToken).get(authn_id)
if qres is None:
msg = "Authentication record with the given `authn_id` (`{}`) not found.".format(
trans.security.encode_id(authn_id))
log.debug(msg)
raise exceptions.ObjectNotFound(msg)
if qres.user_id != trans.user.id:
msg = "The request authentication with ID `{}` is not accessible to user with ID " \
"`{}`.".format(trans.security.encode_id(authn_id), trans.security.encode_id(trans.user.id))
log.warn(msg)
raise exceptions.ItemAccessibilityException(msg)

@staticmethod
def try_get_authz_config(trans, authz_id):
"""
It returns a cloudauthz config (see model.CloudAuthz) with the
given ID; and raise an exception if either a config with given
ID does not exist, or the configuration is defined for a another
user than trans.user.
:type trans: galaxy.web.framework.webapp.GalaxyWebTransaction
:param trans: Galaxy web transaction
:type authz_id: int
:param authz_id: The ID of a CloudAuthz configuration to be used for
getting temporary credentials.
:rtype : model.CloudAuthz
:return: a cloudauthz configuration.
"""
qres = trans.sa_session.query(model.CloudAuthz).get(authz_id)
if qres is None:
raise exceptions.ObjectNotFound("An authorization configuration with given ID not found.")
if trans.user.id != qres.user_id:
msg = "The request authorization configuration (with ID:`{}`) is not accessible for user with " \
"ID:`{}`.".format(qres.id, trans.user.id)
log.warn(msg)
raise exceptions.ItemAccessibilityException(msg)
return qres

def authenticate(self, provider, trans):
"""
:type provider: string
Expand Down Expand Up @@ -143,3 +227,37 @@ def disconnect(self, provider, trans, disconnect_redirect_url=None):
'{}'.format(provider, trans.user.username, str(e))
log.exception(msg)
return False, msg, None

def get_cloud_access_credentials(self, trans, cloudauthz):
"""
This method leverages CloudAuthz (https://github.com/galaxyproject/cloudauthz)
to request a cloud-based resource provider (e.g., Amazon AWS, Microsoft Azure)
for temporary access credentials to a given resource.
It first checks if a cloudauthz config with the given ID (`authz_id`) is
available and can be assumed by the user, and raises an exception if either
is false. Otherwise, it then extends the cloudauthz configuration as required
by the CloudAuthz library for the provider specified in the configuration.
For instance, it adds on-the-fly values such as a valid OpenID Connect
identity token, as required by CloudAuthz for AWS. Then requests temporary
credentials from the CloudAuthz library using the updated configuration.
:type trans: galaxy.web.framework.webapp.GalaxyWebTransaction
:param trans: Galaxy web transaction
:type cloudauthz: CloudAuthz
:param cloudauthz: an instance of CloudAuthz to be used for getting temporary
credentials.
:rtype: dict
:return: a dictionary containing credentials to access a cloud-based
resource provider. See CloudAuthz (https://github.com/galaxyproject/cloudauthz)
for details on the content of this dictionary.
"""
config = self._extend_cloudauthz_config(trans, cloudauthz)
try:
ca = CloudAuthz()
return ca.authorize(cloudauthz.provider, config)
except CloudAuthzBaseException as e:
log.exception(e.message)
raise exceptions.AuthenticationFailed(e.message)
21 changes: 11 additions & 10 deletions lib/galaxy/authnz/psa_authnz.py
Expand Up @@ -107,13 +107,6 @@ def _setup_google_backend(self, oidc_backend_config):
if oidc_backend_config.get('prompt') is not None:
self.config[setting_name('AUTH_EXTRA_ARGUMENTS')]['prompt'] = oidc_backend_config.get('prompt')

def _on_the_fly_config(self, trans):
trans.app.model.PSACode.trans = trans
trans.app.model.UserAuthnzToken.trans = trans
trans.app.model.PSANonce.trans = trans
trans.app.model.PSAPartial.trans = trans
trans.app.model.PSAAssociation.trans = trans

def _get_helper(self, name, do_import=False):
this_config = self.config.get(setting_name(name), DEFAULTS.get(name, None))
return do_import and module_member(this_config) or this_config
Expand All @@ -130,13 +123,13 @@ def _login_user(self, backend, user, social_user):
self.config['user'] = user

def authenticate(self, trans):
self._on_the_fly_config(trans)
on_the_fly_config(trans)
strategy = Strategy(trans, Storage, self.config)
backend = self._load_backend(strategy, self.config['redirect_uri'])
return do_auth(backend)

def callback(self, state_token, authz_code, trans, login_redirect_url):
self._on_the_fly_config(trans)
on_the_fly_config(trans)
self.config[setting_name('LOGIN_REDIRECT_URL')] = login_redirect_url
strategy = Strategy(trans, Storage, self.config)
strategy.session_set(BACKENDS_NAME[self.config['provider']] + '_state', state_token)
Expand All @@ -149,7 +142,7 @@ def callback(self, state_token, authz_code, trans, login_redirect_url):
return redirect_url, self.config.get('user', None)

def disconnect(self, provider, trans, disconnect_redirect_url=None, association_id=None):
self._on_the_fly_config(trans)
on_the_fly_config(trans)
self.config[setting_name('DISCONNECT_REDIRECT_URL')] =\
disconnect_redirect_url if disconnect_redirect_url is not None else ()
strategy = Strategy(trans, Storage, self.config)
Expand Down Expand Up @@ -242,6 +235,14 @@ def is_integrity_error(cls, exception):
return exception.__class__ is IntegrityError


def on_the_fly_config(trans):
trans.app.model.PSACode.trans = trans
trans.app.model.UserAuthnzToken.trans = trans
trans.app.model.PSANonce.trans = trans
trans.app.model.PSAPartial.trans = trans
trans.app.model.PSAAssociation.trans = trans


def contains_required_data(response=None, is_new=False, **kwargs):
"""
This function is called as part of authentication and authorization
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/dependencies/pipfiles/default/Pipfile
Expand Up @@ -71,6 +71,7 @@ pyparsing = "*"
paramiko = "*"
python-genomespaceclient = "<2.0"
social_auth_core = {version = "==1.5.0", extras = ['openidconnect']}
cloudauthz = "<=0.2.0"

[requires]
python_version = "2.7"
Expand Up @@ -13,7 +13,7 @@ funcsigs==1.0.2; python_version < '3.3'
idna==2.7
imagesize==1.1.0; python_version != '3.3.*'
jinja2==2.10
lxml==4.2.4
lxml==4.2.5
markupsafe==1.0
mock==2.0.0
more-itertools==4.3.0
Expand Down
13 changes: 7 additions & 6 deletions lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt
Expand Up @@ -36,6 +36,7 @@ cffi==1.11.5
chardet==3.0.4
cheetah3==3.1.0
cliff==2.13.0; python_version >= '2.6'
cloudauthz==0.2.0
cloudbridge==1.0.1
cmd2==0.8.9
contextlib2==0.5.5; python_version < '3.5'
Expand All @@ -61,10 +62,10 @@ ipaddress==1.0.22; python_version < '3.3'
iso8601==0.1.12
isodate==0.6.0
jmespath==0.9.3
jsonpatch==1.23; python_version != '3.3.*'
jsonpointer==2.0; python_version != '3.3.*'
jsonpatch==1.23
jsonpointer==2.0
jsonschema==2.6.0
keystoneauth1==3.10.0
keystoneauth1==3.11.0
kombu==4.2.1
mako==1.0.7
markupsafe==1.0
Expand All @@ -79,8 +80,8 @@ netifaces==0.10.7
nose==1.3.7
numpy==1.15.1
oauthlib==2.1.0
openstacksdk==0.17.0
os-client-config==1.31.2
openstacksdk==0.17.0; python_version >= '2.6'
os-client-config==1.31.2; python_version >= '2.6'
os-service-types==1.3.0
osc-lib==1.11.1
oslo.config==6.4.0
Expand Down Expand Up @@ -136,7 +137,7 @@ simplejson==3.16.0
six==1.11.0
social-auth-core==1.5.0
sqlalchemy-migrate==0.11.0
sqlalchemy-utils==0.33.3
sqlalchemy-utils==0.33.4
sqlalchemy==1.2.11
sqlparse==0.2.4
stevedore==1.29.0
Expand Down

0 comments on commit 5355fe9

Please sign in to comment.