117 lines
3.3 KiB
Python
117 lines
3.3 KiB
Python
import logging
|
|
|
|
from functools import wraps
|
|
from flask import request, make_response, _request_ctx_stack, abort, session
|
|
from flask.ext.principal import identity_changed, Identity
|
|
from base64 import b64decode
|
|
|
|
from data import model
|
|
from app import app
|
|
|
|
from util.names import parse_namespace_repository
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_authenticated_user():
|
|
return getattr(_request_ctx_stack.top, 'authenticated_user', None)
|
|
|
|
|
|
def get_validated_token():
|
|
return getattr(_request_ctx_stack.top, 'validated_token', None)
|
|
|
|
|
|
def process_basic_auth(auth):
|
|
normalized = [part.strip() for part in auth.split(' ') if part]
|
|
if normalized[0].lower() != 'basic' or len(normalized) != 2:
|
|
logger.debug('Invalid basic auth format.')
|
|
return
|
|
|
|
credentials = b64decode(normalized[1]).split(':')
|
|
|
|
if len(credentials) != 2:
|
|
logger.debug('Invalid basic auth credential formet.')
|
|
|
|
authenticated = model.verify_user(credentials[0], credentials[1])
|
|
|
|
if authenticated:
|
|
logger.debug('Successfully validated user: %s' % authenticated.username)
|
|
ctx = _request_ctx_stack.top
|
|
ctx.authenticated_user = authenticated
|
|
|
|
identity_changed.send(app, identity=Identity(authenticated.username,
|
|
'username'))
|
|
return
|
|
|
|
# We weren't able to authenticate via basic auth.
|
|
logger.debug('Basic auth present but could not be validated.')
|
|
abort(401)
|
|
|
|
|
|
def process_token(auth):
|
|
normalized = [part.strip() for part in auth.split(' ') if part]
|
|
if normalized[0].lower() != 'token' or len(normalized) != 2:
|
|
logger.debug('Invalid token format.')
|
|
return
|
|
|
|
token_details = normalized[1].split(',')
|
|
|
|
if len(token_details) != 2:
|
|
logger.debug('Invalid token format.')
|
|
return
|
|
|
|
token_vals = {val[0]: val[1] for val in
|
|
(detail.split('=') for detail in token_details)}
|
|
if ('signature' not in token_vals or 'repository' not in token_vals):
|
|
logger.debug('Invalid token components.')
|
|
return
|
|
|
|
unquoted = token_vals['repository'][1:-1]
|
|
namespace, repository = parse_namespace_repository(unquoted)
|
|
logger.debug('Validing signature: %s' % token_vals['signature'])
|
|
validated = model.verify_token(token_vals['signature'], namespace,
|
|
repository)
|
|
|
|
if validated:
|
|
session['repository'] = repository
|
|
session['namespace'] = namespace
|
|
|
|
logger.debug('Successfully validated token: %s' % validated.code)
|
|
ctx = _request_ctx_stack.top
|
|
ctx.validated_token = validated
|
|
|
|
identity_changed.send(app, identity=Identity(validated.code, 'token'))
|
|
|
|
return
|
|
|
|
# WE weren't able to authenticate the token
|
|
logger.debug('Token present but could not be validated.')
|
|
abort(401)
|
|
|
|
|
|
def process_auth(f):
|
|
@wraps(f)
|
|
def wrapper(*args, **kwargs):
|
|
auth = request.headers.get('authorization', '')
|
|
|
|
if auth:
|
|
logger.debug('Validating auth header: %s' % auth)
|
|
process_token(auth)
|
|
process_basic_auth(auth)
|
|
else:
|
|
logger.debug('No auth header.')
|
|
|
|
return f(*args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def extract_namespace_repo_from_session(f):
|
|
@wraps(f)
|
|
def wrapper(*args, **kwargs):
|
|
if 'namespace' not in session or 'repository' not in session:
|
|
logger.debug('Unable to load namespace or repository from session.')
|
|
abort(400)
|
|
|
|
return f(session['namespace'], session['repository'], *args, **kwargs)
|
|
return wrapper
|