flesh out oauth provider

- avoid logging credentials
- implement some missing methods
This commit is contained in:
Min RK
2018-09-10 14:26:40 +02:00
parent 4fc4cfe2cc
commit 5e2c133669
3 changed files with 38 additions and 38 deletions

View File

@@ -10,7 +10,7 @@
# E402: module level import not at top of file
# I100: Import statements are in the wrong order
# I101: Imported names are in the wrong order. Should be
ignore = E, C, W, F401, F403, F811, F841, E402, I100, I101
ignore = E, C, W, F401, F403, F811, F841, E402, I100, I101, D400
exclude =
.cache,

View File

@@ -49,9 +49,10 @@ class JupyterHubRequestValidator(RequestValidator):
.first()
)
if oauth_client is None:
raise web.HTTPError(400, "Bad OAuth Client")
return False
if not compare_token(oauth_client.secret, client_secret):
raise web.HTTPError(400, "Bad OAuth Client")
app_log.warning("Client secret mismatch for %s", client_id)
return False
request.client = oauth_client
return True
@@ -75,6 +76,7 @@ class JupyterHubRequestValidator(RequestValidator):
.first()
)
if orm_client is None:
app_log.warning("No such oauth client %s", client_id)
return False
request.client = orm_client
return True
@@ -96,8 +98,8 @@ class JupyterHubRequestValidator(RequestValidator):
Method is used by:
- Authorization Code Grant (during token request)
"""
app_log.debug("confirm_redirect_uri: client_id=%s, code=%s, redirect_uri=%s",
client_id, code, redirect_uri,
app_log.debug("confirm_redirect_uri: client_id=%s, redirect_uri=%s",
client_id, redirect_uri,
)
orm_client = (
self.db
@@ -106,9 +108,13 @@ class JupyterHubRequestValidator(RequestValidator):
.first()
)
if orm_client is None:
app_log.warning("No such oauth client %s", client_id)
return False
if redirect_uri == orm_client.redirect_uri:
return True
else:
app_log.warning("Redirect uri %s != %s", redirect_uri, orm_client.redirect_uri)
return False
# TODO: confirm redirect uri
return True
def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
"""Get the default redirect URI for the client.
@@ -126,7 +132,7 @@ class JupyterHubRequestValidator(RequestValidator):
.first()
)
if orm_client is None:
return False
raise KeyError(client_id)
return orm_client.redirect_uri
def get_default_scopes(self, client_id, request, *args, **kwargs):
@@ -150,8 +156,7 @@ class JupyterHubRequestValidator(RequestValidator):
Method is used by:
- Refresh token grant
"""
token = find_token()
return token.scopes
raise NotImplementedError()
def is_within_original_scope(self, request_scopes, refresh_token, request, *args, **kwargs):
"""Check if requested scopes are within a scope of the refresh token.
@@ -169,8 +174,7 @@ class JupyterHubRequestValidator(RequestValidator):
Method is used by:
- Refresh token grant
"""
return set(request_scopes)
return False
raise NotImplementedError()
def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
"""Invalidate an authorization code after use.
@@ -180,7 +184,7 @@ class JupyterHubRequestValidator(RequestValidator):
Method is used by:
- Authorization Code Grant
"""
app_log.debug("Deleting oauth code %s for %s", code, client_id)
app_log.debug("Deleting oauth code %s... for %s", code[:3], client_id)
orm_code = self.db.query(orm.OAuthCode).filter_by(code=code).first()
if orm_code is not None:
self.db.delete(orm_code)
@@ -221,7 +225,8 @@ class JupyterHubRequestValidator(RequestValidator):
Method is used by:
- Authorization Code Grant
"""
app_log.debug("Saving authorization code %s, %s, %s, %s", client_id, code, args, kwargs)
log_code = code.get('code', 'undefined')[:3] + '...'
app_log.debug("Saving authorization code %s, %s, %s, %s", client_id, log_code, args, kwargs)
orm_client = (
self.db
.query(orm.OAuthClient)
@@ -236,6 +241,8 @@ class JupyterHubRequestValidator(RequestValidator):
code=code['code'],
# oauth has 5 minutes to complete
expires_at=int(datetime.utcnow().timestamp() + 300),
# TODO: persist oauth scopes
# scopes=request.scopes,
user=request.user.orm_user,
redirect_uri=orm_client.redirect_uri,
session_id=request.session_id,
@@ -260,7 +267,7 @@ class JupyterHubRequestValidator(RequestValidator):
Method is used by:
- Authorization Token Grant Dispatcher
"""
return []
raise NotImplementedError("TODO")
def save_token(self, token, request, *args, **kwargs):
"""Persist the token with a token type specific method.
@@ -307,7 +314,19 @@ class JupyterHubRequestValidator(RequestValidator):
- Resource Owner Password Credentials Grant (might not associate a client)
- Client Credentials grant
"""
app_log.debug("Saving bearer token %s", token)
log_token = {}
log_token.update(token)
scopes = token['scope'].split(' ')
# TODO:
if scopes != ['identify']:
raise ValueError("Only 'identify' scope is supported")
# redact sensitive keys in log
for key in ('access_token', 'refresh_token', 'state'):
if key in token:
value = token[key]
if isinstance(value, str):
log_token[key] = 'REDACTED'
app_log.debug("Saving bearer token %s", log_token)
if request.user is None:
raise ValueError("No user for access token: %s" % request.user)
client = self.db.query(orm.OAuthClient).filter_by(identifier=request.client.client_id).first()
@@ -316,7 +335,8 @@ class JupyterHubRequestValidator(RequestValidator):
grant_type=orm.GrantType.authorization_code,
expires_at=datetime.utcnow().timestamp() + token['expires_in'],
refresh_token=token['refresh_token'],
# refresh_expires_at=access_token.refresh_expires_at,
# TODO: save scopes,
# scopes=scopes,
token=token['access_token'],
session_id=request.session_id,
user=request.user,
@@ -507,27 +527,6 @@ class JupyterHubRequestValidator(RequestValidator):
"""
return True
class HashComparable:
"""An object for storing hashed tokens
Overrides `==` so that it compares as equal to its unhashed original
Needed for storing hashed client_secrets
because python-oauth2 uses::
secret == client.client_secret
and we don't want to store unhashed secrets in the database.
"""
def __init__(self, hashed_token):
self.hashed_token = hashed_token
def __repr__(self):
return "<{} '{}'>".format(self.__class__.__name__, self.hashed_token)
def __eq__(self, other):
return compare_token(self.hashed_token, other)
class JupyterHubOAuthServer(WebApplicationServer):
def __init__(self, db, validator, *args, **kwargs):

View File

@@ -469,6 +469,7 @@ class OAuthAccessToken(Hashed, Base):
grant_type = Column(Enum(GrantType), nullable=False)
expires_at = Column(Integer)
refresh_token = Column(Unicode(255))
# TODO: drop refresh_expires_at. Refresh tokens shouldn't expire
refresh_expires_at = Column(Integer)
user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'))
service = None # for API-equivalence with APIToken