Skip to content

Commit

Permalink
Merge pull request #6606 from VJalili/refresh_token
Browse files Browse the repository at this point in the history
Refresh an expired OIDC ID token
  • Loading branch information
jmchilton committed Aug 27, 2018
2 parents af99f49 + 9d7f50c commit 660a514
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 4 deletions.
69 changes: 69 additions & 0 deletions lib/galaxy/authnz/psa_authnz.py
Expand Up @@ -5,6 +5,7 @@
from social_core.utils import module_member, setting_name
from sqlalchemy.exc import IntegrityError

from galaxy.exceptions import MalformedContents
from ..authnz import IdentityProvider
from ..model import PSAAssociation, PSACode, PSANonce, PSAPartial, UserAuthnzToken

Expand Down Expand Up @@ -40,6 +41,10 @@
# defined).
'social_core.pipeline.social_auth.auth_allowed',

# Checks if the decoded response contains all the required fields such
# as an ID token or a refresh token.
'galaxy.authnz.psa_authnz.contains_required_data',

# Checks if the current social-account is already associated in the site.
'social_core.pipeline.social_auth.social_user',

Expand Down Expand Up @@ -237,6 +242,70 @@ def is_integrity_error(cls, exception):
return exception.__class__ is IntegrityError


def contains_required_data(response=None, is_new=False, **kwargs):
"""
This function is called as part of authentication and authorization
pipeline before user is authenticated or authorized (see AUTH_PIPELINE).
This function asserts if all the data required by Galaxy for a user
is provided. It raises an exception if any of the required data is missing,
and returns void if otherwise.
:type response: dict
:param response: a dictionary containing decoded response from
OIDC backend that contain the following keys
among others:
- id_token; see: http://openid.net/specs/openid-connect-core-1_0.html#IDToken
- access_token; see: https://tools.ietf.org/html/rfc6749#section-1.4
- refresh_token; see: https://tools.ietf.org/html/rfc6749#section-1.5
- token_type; see: https://tools.ietf.org/html/rfc6750#section-6.1.1
- scope; see: http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
- expires_in; is the expiration time of the access and ID tokens in seconds since
the response was generated.
:type is_new: bool
:param is_new: has the user been authenticated?
:param kwargs: may contain the following keys among others:
- uid: user ID
- user: Galaxy user; if user is already authenticated
- backend: the backend that is used for user authentication.
- storage: an instance of Storage class.
- strategy: an instance of the Strategy class.
- state: the state code received from identity provider.
- details: details about the user's third-party identity as requested in `scope`.
:rtype: void
:return: Raises an exception if any of the required arguments is missing, and pass if all are given.
"""
hint_msg = "Visit the identity provider's permitted applications page " \
"(e.g., visit `https://myaccount.google.com/u/0/permissions` " \
"for Google), then revoke the access of this Galaxy instance, " \
"and then retry to login. If the problem persists, contact " \
"the Admin of this Galaxy instance."
if response is None or not isinstance(response, dict):
# This can happen only if PSA is not able to decode the `authnz code`
# sent back from the identity provider. PSA internally handles such
# scenarios; however, this case is implemented to prevent uncaught
# server-side errors.
raise MalformedContents(err_msg="`response` not found. {}".format(hint_msg))
if not response.get("id_token"):
# This can happen if a non-OIDC compliant backend is used;
# e.g., an OAuth2.0-based backend that only generates access token.
raise MalformedContents(err_msg="Missing identity token. {}".format(hint_msg))
if is_new and not response.get("refresh_token"):
# An identity provider (e.g., Google) sends a refresh token the first
# time user consents Galaxy's access (i.e., the first time user logs in
# to a galaxy instance using their credentials with the identity provider).
# There could be variety of scenarios under which a refresh token might
# be missing; e.g., a manipulated Galaxy's database, where a user's records
# from galaxy_user and oidc_user_authnz_tokens tables deleted after the
# user has provided consent. This can also happen under dev efforts.
# The solution is to revoke the consent by visiting the identity provider's
# website, and then retry the login process.
raise MalformedContents(err_msg="Missing refresh token. {}".format(hint_msg))


def allowed_to_disconnect(name=None, user=None, user_storage=None, strategy=None,
backend=None, request=None, details=None, **kwargs):
"""
Expand Down
9 changes: 5 additions & 4 deletions lib/galaxy/model/__init__.py
Expand Up @@ -4807,12 +4807,13 @@ def __init__(self, provider, uid, extra_data=None, lifetime=None, assoc_type=Non
self.lifetime = lifetime
self.assoc_type = assoc_type

def get_id_token(self):
def get_id_token(self, strategy):
if self.access_token_expired():
# Access and ID tokens have same expiration time;
# hence, if one is expired, the other is expired too.
self.refresh_token(strategy)
return self.extra_data.get('id_token', None) if self.extra_data is not None else None

def get_access_token(self):
return self.extra_data.get('access_token', None) if self.extra_data is not None else None

def set_extra_data(self, extra_data=None):
if super(UserAuthnzToken, self).set_extra_data(extra_data):
self.trans.sa_session.add(self)
Expand Down

0 comments on commit 660a514

Please sign in to comment.