Skip to content

Commit

Permalink
Merge e289b82 into f174024
Browse files Browse the repository at this point in the history
  • Loading branch information
vpsx committed Jul 9, 2020
2 parents f174024 + e289b82 commit a02e08f
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 31 deletions.
2 changes: 1 addition & 1 deletion fence/blueprints/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def authorize(*args, **kwargs):
try:
grant = server.validate_consent_request(end_user=user)
except OAuth2Error as e:
raise Unauthorized("{} failed to authorize".format(str(e)))
raise Unauthorized("Failed to authorize: {}".format(str(e)))

client_id = grant.client.client_id
with flask.current_app.db.session as session:
Expand Down
55 changes: 51 additions & 4 deletions fence/jwt/token.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import json
import time
import uuid
from enum import Enum

from authlib.common.encoding import to_unicode
from authlib.oidc.core import CodeIDToken as AuthlibCodeIDToken
from authlib.oidc.core import IDToken, CodeIDToken, ImplicitIDToken
from authlib.oidc.core.util import create_half_hash

from cdislogging import get_logger
import flask
import jwt
Expand All @@ -27,6 +30,11 @@
}


class AuthFlowTypes(Enum):
CODE = 1
IMPLICIT = 2


class JWTResult(object):
"""
Just a container for the results necessary to keep track of from generating
Expand All @@ -39,7 +47,7 @@ def __init__(self, token=None, kid=None, claims=None):
self.claims = claims


class UnsignedIDToken(AuthlibCodeIDToken):
class UnsignedIDToken(IDToken):
def __init__(self, token, header=None, **kwargs):
header = header or {}
super(UnsignedIDToken, self).__init__(token, header, **kwargs)
Expand Down Expand Up @@ -117,6 +125,14 @@ def from_signed_and_encoded_token(
return token


class UnsignedCodeIDToken(UnsignedIDToken, CodeIDToken):
pass


class UnsignedImplicitIDToken(UnsignedIDToken, ImplicitIDToken):
pass


def issued_and_expiration_times(seconds_to_expire):
"""
Return the times in unix time that a token is being issued and will be
Expand Down Expand Up @@ -191,6 +207,8 @@ def generate_signed_id_token(
max_age=None,
nonce=None,
include_project_access=True,
auth_flow_type=AuthFlowTypes.CODE,
access_token=None,
**kwargs
):
"""
Expand Down Expand Up @@ -224,6 +242,8 @@ def generate_signed_id_token(
auth_time=auth_time,
max_age=max_age,
nonce=nonce,
auth_flow_type=auth_flow_type,
access_token=access_token,
**kwargs
)
signed_token = token.get_signed_and_encoded_token(kid, private_key)
Expand Down Expand Up @@ -428,6 +448,8 @@ def generate_id_token(
max_age=None,
nonce=None,
include_project_access=True,
auth_flow_type=AuthFlowTypes.CODE,
access_token=None,
**kwargs
):
"""
Expand All @@ -450,6 +472,7 @@ def generate_id_token(
Returns:
UnsignedIDToken: Unsigned ID token
"""

iat, exp = issued_and_expiration_times(expires_in)
issuer = config.get("BASE_URL")

Expand Down Expand Up @@ -508,13 +531,37 @@ def generate_id_token(
if include_project_access:
claims["context"]["user"]["projects"] = dict(user.project_access)

if access_token:
at_hash = to_unicode(create_half_hash(access_token, "RS256"))
claims["at_hash"] = at_hash

logger.info("issuing JWT ID token\n" + json.dumps(claims, indent=4))

token_options = {
"iss": {"essential": True, "value": config.get("BASE_URL")},
"nonce": {"value": nonce},
"nonce": {
"essential": auth_flow_type == AuthFlowTypes.IMPLICIT,
"value": nonce,
},
}
token = UnsignedIDToken(claims, options=token_options)
if auth_flow_type == AuthFlowTypes.IMPLICIT:
token = UnsignedImplicitIDToken(
claims,
header={"alg": "RS256"},
options=token_options,
params={"access_token": access_token},
)
else:
if auth_flow_type != AuthFlowTypes.CODE:
logger.error(
"Invalid auth_flow_type passed to generate_id_token. Assuming code flow."
)
token = UnsignedCodeIDToken(
claims,
header={"alg": "RS256"},
options=token_options,
params={"access_token": access_token},
)
token.validate()

return token
51 changes: 28 additions & 23 deletions fence/oidc/jwt_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from flask_sqlalchemy_session import current_session

from fence.jwt.token import (
AuthFlowTypes,
generate_signed_access_token,
generate_signed_id_token,
generate_signed_refresh_token,
Expand Down Expand Up @@ -80,28 +81,11 @@ def generate_implicit_response(
if not "user" in scope:
scope.append("user")

# don't provide user projects access in id_tokens for implicit flow
# due to issues with "Location" header size during redirect (and b/c
# of general deprecation of user access information in tokens)
id_token = generate_signed_id_token(
kid=keypair.kid,
private_key=keypair.private_key,
user=user,
expires_in=config["ACCESS_TOKEN_EXPIRES_IN"],
client_id=client.client_id,
audiences=scope,
nonce=nonce,
linked_google_email=linked_google_email,
linked_google_account_exp=linked_google_account_exp,
include_project_access=False,
).token

# ``expires_in`` is just the token expiration time.
expires_in = config["ACCESS_TOKEN_EXPIRES_IN"]

response = {
"token_type": "Bearer",
"id_token": id_token,
"expires_in": expires_in,
# "state" handled in authlib
}
Expand All @@ -122,6 +106,25 @@ def generate_implicit_response(
).token
response["access_token"] = access_token

# don't provide user projects access in id_tokens for implicit flow
# due to issues with "Location" header size during redirect (and b/c
# of general deprecation of user access information in tokens)
id_token = generate_signed_id_token(
kid=keypair.kid,
private_key=keypair.private_key,
user=user,
expires_in=config["ACCESS_TOKEN_EXPIRES_IN"],
client_id=client.client_id,
audiences=scope,
nonce=nonce,
linked_google_email=linked_google_email,
linked_google_account_exp=linked_google_account_exp,
include_project_access=False,
auth_flow_type=AuthFlowTypes.IMPLICIT,
access_token=access_token if include_access_token else None,
).token
response["id_token"] = id_token

return response


Expand Down Expand Up @@ -173,25 +176,27 @@ def generate_token_response(
if not isinstance(scope, list):
scope = scope.split(" ")

id_token = generate_signed_id_token(
access_token = generate_signed_access_token(
kid=keypair.kid,
private_key=keypair.private_key,
user=user,
expires_in=config["ACCESS_TOKEN_EXPIRES_IN"],
scopes=scope,
client_id=client.client_id,
audiences=scope,
nonce=nonce,
linked_google_email=linked_google_email,
linked_google_account_exp=linked_google_account_exp,
).token
access_token = generate_signed_access_token(
id_token = generate_signed_id_token(
kid=keypair.kid,
private_key=keypair.private_key,
user=user,
expires_in=config["ACCESS_TOKEN_EXPIRES_IN"],
scopes=scope,
client_id=client.client_id,
audiences=scope,
nonce=nonce,
linked_google_email=linked_google_email,
linked_google_account_exp=linked_google_account_exp,
auth_flow_type=AuthFlowTypes.CODE,
access_token=access_token,
).token
# If ``refresh_token`` was passed (for instance from the refresh
# grant), use that instead of generating a new one.
Expand Down
10 changes: 7 additions & 3 deletions tests/oidc/core/token/test_id_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

from authlib.jose.errors import InvalidClaimError

from fence.jwt.token import generate_signed_id_token, UnsignedIDToken
from fence.jwt.token import (
generate_signed_id_token,
UnsignedCodeIDToken,
UnsignedIDToken,
)
from fence.jwt.validate import validate_jwt
from fence.models import User
from fence.utils import random_str
Expand Down Expand Up @@ -34,7 +38,7 @@ def test_recode_id_token(app, kid, rsa_private_key):
max_age=max_age,
nonce=nonce,
)
original_unsigned_token = UnsignedIDToken.from_signed_and_encoded_token(
original_unsigned_token = UnsignedCodeIDToken.from_signed_and_encoded_token(
original_signed_token.token,
client_id=client_id,
issuer=issuer,
Expand All @@ -45,7 +49,7 @@ def test_recode_id_token(app, kid, rsa_private_key):
new_signed_token = original_unsigned_token.get_signed_and_encoded_token(
kid, rsa_private_key
)
new_unsigned_token = UnsignedIDToken.from_signed_and_encoded_token(
new_unsigned_token = UnsignedCodeIDToken.from_signed_and_encoded_token(
new_signed_token,
client_id=client_id,
issuer=issuer,
Expand Down

0 comments on commit a02e08f

Please sign in to comment.