Refactor auth code to be cleaner and more extensible

We move all the auth handling, serialization and deserialization into a new AuthContext interface, and then standardize a registration model for handling of specific auth context types (user, robot, token, etc).
This commit is contained in:
Joseph Schorr 2018-01-05 16:27:03 -05:00
parent 8ba2e71fb1
commit e220b50543
31 changed files with 822 additions and 436 deletions

View file

@ -7,21 +7,18 @@ from flask import request, url_for
from flask_principal import identity_changed, Identity
from app import app, get_app_url, instance_keys, metric_queue
from auth.auth_context import (set_grant_context, get_grant_context)
from auth.auth_context import set_authenticated_context
from auth.auth_context_type import SignedAuthContext
from auth.permissions import repository_read_grant, repository_write_grant, repository_admin_grant
from util.http import abort
from util.names import parse_namespace_repository
from util.security.registry_jwt import (ANONYMOUS_SUB, decode_bearer_header,
InvalidBearerTokenException)
from data import model
logger = logging.getLogger(__name__)
CONTEXT_KINDS = ['user', 'token', 'oauth', 'app_specific_token']
ACCESS_SCHEMA = {
'type': 'array',
'description': 'List of access granted to the subject',
@ -65,71 +62,6 @@ class InvalidJWTException(Exception):
pass
class GrantedEntity(object):
def __init__(self, user=None, token=None, oauth=None, app_specific_token=None):
self.user = user
self.token = token
self.oauth = oauth
self.app_specific_token = app_specific_token
def get_granted_entity():
""" Returns the entity granted in the current context, if any. Returns the GrantedEntity or None
if none.
"""
context = get_grant_context()
if not context:
return None
kind = context.get('kind', 'anonymous')
if not kind in CONTEXT_KINDS:
return None
if kind == 'app_specific_token':
app_specific_token = model.appspecifictoken.get_token_by_uuid(context.get('ast', ''))
if app_specific_token is None:
return None
return GrantedEntity(app_specific_token=app_specific_token, user=app_specific_token.user)
if kind == 'user':
user = model.user.get_user(context.get('user', ''))
if not user:
return None
return GrantedEntity(user=user)
if kind == 'token':
token = model.token.load_token_data(context.get('token'))
if not token:
return None
return GrantedEntity(token=token)
if kind == 'oauth':
user = model.user.get_user(context.get('user', ''))
if not user:
return None
oauthtoken = model.oauth.lookup_access_token_for_user(user, context.get('oauth', ''))
if not oauthtoken:
return None
return GrantedEntity(oauth=oauthtoken, user=user)
return None
def get_granted_username():
""" Returns the username inside the grant, if any. """
granted = get_granted_entity()
if not granted or not granted.user:
return None
return granted.user.username
def get_auth_headers(repository=None, scopes=None):
""" Returns a dictionary of headers for auth responses. """
headers = {}
@ -198,6 +130,9 @@ def identity_from_bearer_token(bearer_header):
def process_registry_jwt_auth(scopes=None):
""" Processes the registry JWT auth token found in the authorization header. If none found,
no error is returned. If an invalid token is found, raises a 401.
"""
def inner(func):
@wraps(func)
def wrapper(*args, **kwargs):
@ -205,10 +140,15 @@ def process_registry_jwt_auth(scopes=None):
auth = request.headers.get('authorization', '').strip()
if auth:
try:
extracted_identity, context = identity_from_bearer_token(auth)
extracted_identity, context_dict = identity_from_bearer_token(auth)
identity_changed.send(app, identity=extracted_identity)
set_grant_context(context)
logger.debug('Identity changed to %s', extracted_identity.id)
auth_context = SignedAuthContext.build_from_signed_dict(context_dict)
if auth_context is not None:
logger.debug('Auth context set to %s', auth_context.signed_data)
set_authenticated_context(auth_context)
except InvalidJWTException as ije:
repository = None
if 'namespace_name' in kwargs and 'repo_name' in kwargs: