Start of a v2 API.

This commit is contained in:
Jake Moshenko 2015-06-22 17:37:13 -04:00
parent 3bfa2a6509
commit acbcc2e206
16 changed files with 508 additions and 55 deletions

87
auth/jwt_auth.py Normal file
View file

@ -0,0 +1,87 @@
import logging
import jwt
import re
from datetime import datetime, timedelta
from functools import wraps
from flask import request
from flask.ext.principal import identity_changed, Identity
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend
from app import app
from auth_context import set_grant_user_context
from permissions import repository_read_grant, repository_write_grant
from util.names import parse_namespace_repository
logger = logging.getLogger(__name__)
TOKEN_REGEX = re.compile(r'Bearer (([a-zA-Z0-9+/]+\.)+[a-zA-Z0-9+-_/]+)')
def process_jwt_auth(func):
@wraps(func)
def wrapper(*args, **kwargs):
logger.debug('Called with params: %s, %s', args, kwargs)
auth = request.headers.get('authorization', '').strip()
if auth:
logger.debug('Validating auth header: %s', auth)
# Extract the jwt token from the header
match = TOKEN_REGEX.match(auth)
if match is None or match.end() != len(auth):
logger.debug('Not a valid bearer token: %s', auth)
return
encoded = match.group(1)
logger.debug('encoded JWT: %s', encoded)
# Load the JWT returned.
try:
with open('/Users/jake/Projects/registry-v2/ca/quay.host.crt') as cert_file:
cert_obj = load_pem_x509_certificate(cert_file.read(), default_backend())
public_key = cert_obj.public_key()
payload = jwt.decode(encoded, public_key, algorithms=['RS256'], audience='quay',
issuer='token-issuer')
except jwt.InvalidTokenError:
logger.exception('Exception when decoding returned JWT')
return (None, 'Invalid username or password')
if not 'sub' in payload:
raise Exception('Missing subject field in JWT')
if not 'exp' in payload:
raise Exception('Missing exp field in JWT')
# Verify that the expiration is no more than 300 seconds in the future.
if datetime.fromtimestamp(payload['exp']) > datetime.utcnow() + timedelta(seconds=300):
logger.debug('Payload expiration is outside of the 300 second window: %s', payload['exp'])
return (None, 'Invalid username or password')
username = payload['sub']
loaded_identity = Identity(username, 'signed_grant')
# Process the grants from the payload
if 'access' in payload:
for grant in payload['access']:
if grant['type'] != 'repository':
continue
namespace, repo_name = parse_namespace_repository(grant['name'])
if 'push' in grant['actions']:
loaded_identity.provides.add(repository_write_grant(namespace, repo_name))
elif 'pull' in grant['actions']:
loaded_identity.provides.add(repository_read_grant(namespace, repo_name))
identity_changed.send(app, identity=loaded_identity)
set_grant_user_context(username)
logger.debug('Identity changed to %s', username)
else:
logger.debug('No auth header.')
return func(*args, **kwargs)
return wrapper