Skip to content

Commit

Permalink
PXP-6240 Support ga4gh passport claim in userinfo (#792)
Browse files Browse the repository at this point in the history
* feat(ras-visas): add GA4GH visa v1 db table

* feat(ras-visas): save RAS visas to db on RAS login

* feat(ras-visas-uinfo): include ga4gh_passport_v1 claim in userinfo response

* feat(ras-visas-uinfo): leave ga4gh visas encoded in passport claim
  • Loading branch information
vpsx committed Jul 9, 2020
1 parent c4e3114 commit f174024
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 2 deletions.
34 changes: 33 additions & 1 deletion fence/blueprints/login/ras.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import flask
import jwt
from flask_sqlalchemy_session import current_session

from fence.models import IdentityProvider
from fence.models import GA4GHVisaV1, IdentityProvider, User

from fence.blueprints.login.base import DefaultOAuth2Login, DefaultOAuth2Callback

Expand All @@ -19,3 +21,33 @@ def __init__(self):
client=flask.current_app.ras_client,
username_field="username",
)

def post_login(self, user, token_result):

# TODO: I'm not convinced this code should be in post_login.
# Just putting it in here for now, but might refactor later.
# This saves us a call to RAS /userinfo, but will not make sense
# when there is more than one visa issuer.

encoded_visas = flask.g.userinfo.get("ga4gh_passport_v1", [])

for encoded_visa in encoded_visas:
# TODO: These visas must be validated!!!
# i.e. (Remove `verify=False` in jwt.decode call)
# But: need a routine for getting public keys per visa.
# And we probably want to cache them.
# Also needs any ga4gh-specific validation.
# For now just read them without validation:
decoded_visa = jwt.decode(encoded_visa, verify=False)

visa = GA4GHVisaV1(
user=user,
source=decoded_visa["ga4gh_visa_v1"]["source"],
type=decoded_visa["ga4gh_visa_v1"]["type"],
asserted=int(decoded_visa["ga4gh_visa_v1"]["asserted"]),
expires=int(decoded_visa["exp"]),
ga4gh_visa=encoded_visa,
)

current_session.add(visa)
current_session.commit()
21 changes: 21 additions & 0 deletions fence/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,27 @@ class ServiceAccountToGoogleBucketAccessGroup(Base):
)


class GA4GHVisaV1(Base):

__tablename__ = "ga4gh_visa_v1"

# As Fence will consume visas from many visa issuers, will not use jti as pkey
id = Column(BigInteger, primary_key=True)

user_id = Column(Integer, ForeignKey(User.id, ondelete="CASCADE"), nullable=False)
user = relationship(
"User",
backref=backref(
"ga4gh_visas_v1", cascade="all, delete-orphan", passive_deletes=True
),
)
ga4gh_visa = Column(Text, nullable=False) # In encoded form
source = Column(String, nullable=False)
type = Column(String, nullable=False)
asserted = Column(BigInteger, nullable=False)
expires = Column(BigInteger, nullable=False)


to_timestamp = (
"CREATE OR REPLACE FUNCTION pc_datetime_to_timestamp(datetoconvert timestamp) "
"RETURNS BIGINT AS "
Expand Down
4 changes: 4 additions & 0 deletions fence/resources/openid/ras_oauth2.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# need new RAS
import flask
from .idp_oauth2 import Oauth2ClientBase
from jose import jwt
import requests
Expand Down Expand Up @@ -65,6 +66,9 @@ def get_user_id(self, code):

userinfo = self.get_userinfo(token, userinfo_endpoint)

# Save userinfo in flask.g.user for later use in post_login
flask.g.userinfo = userinfo

except Exception as e:
self.logger.exception("{}: {}".format(err_msg, e))
return {"error": err_msg}
Expand Down
8 changes: 7 additions & 1 deletion fence/resources/user/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate

from cdislogging import get_logger
import flask
import jwt
from cdislogging import get_logger

from fence.resources import userdatamodel as udm
from fence.resources.google.utils import (
Expand Down Expand Up @@ -128,6 +129,11 @@ def get_user_info(current_session, username):
optional_info = _get_optional_userinfo(user, requested_userinfo_claims)
info.update(optional_info)

# Include ga4gh passport visas
# TODO: Respect access token claims (only include if ga4gh_passport_v1 scope present)
encoded_visas = [row.ga4gh_visa for row in user.ga4gh_visas_v1]
info["ga4gh_passport_v1"] = encoded_visas

return info


Expand Down

0 comments on commit f174024

Please sign in to comment.