This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/auth/jwt_auth.py
Jake Moshenko bea8b9ac53 More changes for registry-v2 in python.
Implement the minimal changes to the local filesystem storage driver and feed them through the distributed storage driver.
Create a digest package which contains digest_tools and checksums.
Fix the tests to use the new v1 endpoint locations.
Fix repository.delete_instance to properly filter the generated queries to avoid most subquery deletes, but still generate them when not explicitly filtered.
2015-07-17 11:50:41 -04:00

87 lines
No EOL
3 KiB
Python

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