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:
parent
8ba2e71fb1
commit
e220b50543
31 changed files with 822 additions and 436 deletions
|
@ -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:
|
||||
|
|
Reference in a new issue