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_jwt') # 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