Start of a v2 API.
This commit is contained in:
parent
3bfa2a6509
commit
acbcc2e206
16 changed files with 508 additions and 55 deletions
87
auth/jwt_auth.py
Normal file
87
auth/jwt_auth.py
Normal 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
|
Reference in a new issue