Merge pull request #2967 from coreos-inc/joseph.schorr/QS-111/auth-refactor
Refactor auth code to be cleaner and more extensible
This commit is contained in:
commit
7cd2c00d4d
31 changed files with 822 additions and 436 deletions
|
@ -7,9 +7,7 @@ from flask import request
|
|||
|
||||
from app import analytics, userevents, ip_resolver
|
||||
from data import model
|
||||
from auth.registry_jwt_auth import get_granted_entity
|
||||
from auth.auth_context import (get_authenticated_user, get_validated_token,
|
||||
get_validated_oauth_token, get_validated_app_specific_token)
|
||||
from auth.auth_context import get_authenticated_context, get_authenticated_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -22,51 +20,16 @@ def track_and_log(event_name, repo_obj, analytics_name=None, analytics_sample=1,
|
|||
}
|
||||
metadata.update(kwargs)
|
||||
|
||||
# Add auth context metadata.
|
||||
analytics_id = 'anonymous'
|
||||
|
||||
authenticated_oauth_token = get_validated_oauth_token()
|
||||
authenticated_user = get_authenticated_user()
|
||||
authenticated_token = get_validated_token() if not authenticated_user else None
|
||||
app_specific_token = get_validated_app_specific_token()
|
||||
|
||||
if (not authenticated_user and not authenticated_token and not authenticated_oauth_token and
|
||||
not app_specific_token):
|
||||
entity = get_granted_entity()
|
||||
if entity:
|
||||
authenticated_user = entity.user
|
||||
authenticated_token = entity.token
|
||||
authenticated_oauth_token = entity.oauth
|
||||
app_specific_token = entity.app_specific_token
|
||||
|
||||
logger.debug('Logging the %s to Mixpanel and the log system', event_name)
|
||||
if authenticated_oauth_token:
|
||||
metadata['oauth_token_id'] = authenticated_oauth_token.id
|
||||
metadata['oauth_token_application_id'] = authenticated_oauth_token.application.client_id
|
||||
metadata['oauth_token_application'] = authenticated_oauth_token.application.name
|
||||
metadata['username'] = authenticated_user.username
|
||||
analytics_id = 'oauth:{0}'.format(authenticated_oauth_token.id)
|
||||
elif app_specific_token:
|
||||
metadata['app_specific_token'] = app_specific_token.uuid
|
||||
metadata['username'] = authenticated_user.username
|
||||
analytics_id = 'appspecifictoken:{0}'.format(app_specific_token.uuid)
|
||||
elif authenticated_user:
|
||||
metadata['username'] = authenticated_user.username
|
||||
analytics_id = authenticated_user.username
|
||||
elif authenticated_token:
|
||||
metadata['token'] = authenticated_token.friendly_name
|
||||
metadata['token_code'] = authenticated_token.code
|
||||
|
||||
if authenticated_token.kind:
|
||||
metadata['token_type'] = authenticated_token.kind.name
|
||||
|
||||
analytics_id = 'token:{0}'.format(authenticated_token.code)
|
||||
else:
|
||||
metadata['public'] = True
|
||||
analytics_id = 'anonymous'
|
||||
auth_context = get_authenticated_context()
|
||||
if auth_context is not None:
|
||||
analytics_id, context_metadata = auth_context.analytics_id_and_public_metadata()
|
||||
metadata.update(context_metadata)
|
||||
|
||||
# Publish the user event (if applicable)
|
||||
logger.debug('Checking publishing %s to the user events system', event_name)
|
||||
if authenticated_user and not authenticated_user.robot:
|
||||
if auth_context and auth_context.has_nonrobot_user:
|
||||
logger.debug('Publishing %s to the user events system', event_name)
|
||||
user_event_data = {
|
||||
'action': event_name,
|
||||
|
@ -74,7 +37,7 @@ def track_and_log(event_name, repo_obj, analytics_name=None, analytics_sample=1,
|
|||
'namespace': namespace_name,
|
||||
}
|
||||
|
||||
event = userevents.get_event(authenticated_user.username)
|
||||
event = userevents.get_event(auth_context.authed_user.username)
|
||||
event.publish_event_data('docker-cli', user_event_data)
|
||||
|
||||
# Save the action to mixpanel.
|
||||
|
@ -100,6 +63,6 @@ def track_and_log(event_name, repo_obj, analytics_name=None, analytics_sample=1,
|
|||
|
||||
# Log the action to the database.
|
||||
logger.debug('Logging the %s to logs system', event_name)
|
||||
model.log.log_action(event_name, namespace_name, performer=authenticated_user,
|
||||
model.log.log_action(event_name, namespace_name, performer=get_authenticated_user(),
|
||||
ip=request.remote_addr, metadata=metadata, repository=repo_obj)
|
||||
logger.debug('Track and log of %s complete', event_name)
|
||||
|
|
14
util/http.py
14
util/http.py
|
@ -5,7 +5,7 @@ from flask import request, make_response, current_app
|
|||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from app import analytics
|
||||
from auth.auth_context import get_authenticated_user, get_validated_token
|
||||
from auth.auth_context import get_authenticated_context
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -58,15 +58,9 @@ def abort(status_code, message=None, issue=None, headers=None, **kwargs):
|
|||
params['message'] = message
|
||||
|
||||
# Add the user information.
|
||||
auth_user = get_authenticated_user()
|
||||
auth_token = get_validated_token()
|
||||
if auth_user:
|
||||
analytics.track(auth_user.username, 'http_error', params)
|
||||
message = '%s (user: %s)' % (message, auth_user.username)
|
||||
elif auth_token:
|
||||
analytics.track(auth_token.code, 'http_error', params)
|
||||
message = '%s (token: %s)' % (message,
|
||||
auth_token.friendly_name or auth_token.code)
|
||||
auth_context = get_authenticated_context()
|
||||
if auth_context is not None:
|
||||
message = '%s (authorized: %s)' % (message, auth_context.description)
|
||||
|
||||
# Log the abort.
|
||||
logger.error('Error %s: %s; Arguments: %s' % (status_code, message, params))
|
||||
|
|
|
@ -106,50 +106,21 @@ def _generate_jwt_object(audience, subject, context, access, lifetime_s, issuer,
|
|||
return jwt.encode(token_data, private_key, ALGORITHM, headers=token_headers)
|
||||
|
||||
|
||||
def build_context_and_subject(user=None, token=None, oauthtoken=None, appspecifictoken=None,
|
||||
tuf_root=None):
|
||||
def build_context_and_subject(auth_context=None, tuf_root=None):
|
||||
""" Builds the custom context field for the JWT signed token and returns it,
|
||||
along with the subject for the JWT signed token. """
|
||||
|
||||
# Serialize to a dictionary.
|
||||
context = auth_context.to_signed_dict() if auth_context else {}
|
||||
|
||||
# Default to quay root if not explicitly granted permission to see signer root
|
||||
if not tuf_root:
|
||||
tuf_root = QUAY_TUF_ROOT
|
||||
|
||||
context = {
|
||||
CLAIM_TUF_ROOT: tuf_root
|
||||
}
|
||||
|
||||
if oauthtoken:
|
||||
context.update({
|
||||
'kind': 'oauth',
|
||||
'user': user.username,
|
||||
'oauth': oauthtoken.uuid,
|
||||
})
|
||||
return (context, user.username)
|
||||
|
||||
if appspecifictoken:
|
||||
context.update({
|
||||
'kind': 'app_specific_token',
|
||||
'user': user.username,
|
||||
'ast': appspecifictoken.uuid,
|
||||
})
|
||||
return (context, user.username)
|
||||
|
||||
if user:
|
||||
context.update({
|
||||
'kind': 'user',
|
||||
'user': user.username,
|
||||
})
|
||||
return (context, user.username)
|
||||
|
||||
if token:
|
||||
context.update({
|
||||
'kind': 'token',
|
||||
'token': token.code,
|
||||
})
|
||||
return (context, None)
|
||||
|
||||
context.update({
|
||||
'kind': 'anonymous',
|
||||
CLAIM_TUF_ROOT: tuf_root
|
||||
})
|
||||
return (context, ANONYMOUS_SUB)
|
||||
|
||||
if not auth_context or auth_context.is_anonymous:
|
||||
return (context, ANONYMOUS_SUB)
|
||||
|
||||
return (context, auth_context.authed_user.username if auth_context.authed_user else None)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import logging
|
||||
|
||||
from urlparse import urljoin
|
||||
from posixpath import join
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from six import add_metaclass
|
||||
from urlparse import urljoin
|
||||
from posixpath import join
|
||||
|
||||
import requests
|
||||
|
||||
|
@ -11,7 +12,8 @@ from data.database import CloseForLongOperation
|
|||
from util.abchelpers import nooper
|
||||
from util.failover import failover, FailoverException
|
||||
from util.security.instancekeys import InstanceKeys
|
||||
from util.security.registry_jwt import build_context_and_subject, generate_bearer_token, QUAY_TUF_ROOT, SIGNER_TUF_ROOT
|
||||
from util.security.registry_jwt import (build_context_and_subject, generate_bearer_token,
|
||||
SIGNER_TUF_ROOT)
|
||||
|
||||
|
||||
DEFAULT_HTTP_HEADERS = {'Connection': 'close'}
|
||||
|
@ -223,7 +225,7 @@ class ImplementedTUFMetadataAPI(TUFMetadataAPIInterface):
|
|||
except Non200ResponseException as ex:
|
||||
logger.exception('Failed request for %s: %s', gun, str(ex))
|
||||
except InvalidMetadataException as ex:
|
||||
logger.exception('Failed to parse targets from metadata', str(ex))
|
||||
logger.exception('Failed to parse targets from metadata: %s', str(ex))
|
||||
return None
|
||||
|
||||
def _parse_signed(self, json_response):
|
||||
|
@ -240,7 +242,7 @@ class ImplementedTUFMetadataAPI(TUFMetadataAPIInterface):
|
|||
'name': gun,
|
||||
'actions': actions,
|
||||
}]
|
||||
context, subject = build_context_and_subject(user=None, token=None, oauthtoken=None, tuf_root=SIGNER_TUF_ROOT)
|
||||
context, subject = build_context_and_subject(auth_context=None, tuf_root=SIGNER_TUF_ROOT)
|
||||
token = generate_bearer_token(self._config["SERVER_HOSTNAME"], subject, context, access,
|
||||
TOKEN_VALIDITY_LIFETIME_S, self._instance_keys)
|
||||
return {'Authorization': 'Bearer %s' % token}
|
||||
|
|
Reference in a new issue