Add a test to verify that all important blueprints have all their methods decorated

This ensures that we don't accidentally add a blueprint method without either explicitly blacklisting or whitelisting anonymous access
This commit is contained in:
Joseph Schorr 2015-06-02 15:56:44 -04:00
parent 075c75d031
commit 477a3fdcdc
6 changed files with 57 additions and 4 deletions

View file

@ -19,7 +19,7 @@ from auth import scopes
from auth.auth_context import get_authenticated_user, get_validated_oauth_token
from auth.auth import process_oauth
from endpoints.csrf import csrf_protect
from endpoints.decorators import anon_protect
from endpoints.decorators import check_anon_protection
logger = logging.getLogger(__name__)
@ -229,14 +229,14 @@ def parse_repository_name(func):
class ApiResource(Resource):
method_decorators = [anon_protect]
method_decorators = [check_anon_protection]
def options(self):
return None, 200
class RepositoryParamResource(ApiResource):
method_decorators = [anon_protect, parse_repository_name]
method_decorators = [check_anon_protection, parse_repository_name]
def require_repo_permission(permission_class, scope, allow_public=False):

View file

@ -15,6 +15,12 @@ def anon_allowed(func):
def anon_protect(func):
""" Marks a method as requiring some form of valid user auth before it can be executed. """
func.__anon_protected = True
return check_anon_protection(func)
def check_anon_protection(func):
""" Validates a method as requiring some form of valid user auth before it can be executed. """
@wraps(func)
def wrapper(*args, **kwargs):
# Skip if anonymous access is allowed.

View file

@ -20,7 +20,7 @@ from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission,
from util.http import abort
from endpoints.trackhelper import track_and_log
from endpoints.notificationhelper import spawn_notification
from endpoints.decorators import anon_protect
from endpoints.decorators import anon_protect, anon_allowed
import features
@ -74,6 +74,7 @@ def generate_headers(scope=GrantType.READ_REPOSITORY):
@index.route('/users', methods=['POST'])
@index.route('/users/', methods=['POST'])
@anon_allowed
def create_user():
user_data = request.get_json()
if not user_data or not 'username' in user_data:
@ -146,6 +147,7 @@ def create_user():
@index.route('/users', methods=['GET'])
@index.route('/users/', methods=['GET'])
@process_auth
@anon_allowed
def get_user():
if get_validated_oauth_token():
return jsonify({
@ -167,6 +169,7 @@ def get_user():
@index.route('/users/<username>/', methods=['PUT'])
@process_auth
@anon_allowed
def update_user(username):
permission = UserAdminPermission(username)
@ -194,6 +197,7 @@ def update_user(username):
@process_auth
@parse_repository_name
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
@anon_allowed
def create_repository(namespace, repository):
logger.debug('Parsing image descriptions')
image_descriptions = json.loads(request.data.decode('utf8'))
@ -246,6 +250,7 @@ def create_repository(namespace, repository):
@process_auth
@parse_repository_name
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
@anon_allowed
def update_images(namespace, repository):
permission = ModifyRepositoryPermission(namespace, repository)
@ -304,12 +309,14 @@ def get_repository_images(namespace, repository):
@process_auth
@parse_repository_name
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
@anon_allowed
def delete_repository_images(namespace, repository):
abort(501, 'Not Implemented', issue='not-implemented')
@index.route('/repositories/<path:repository>/auth', methods=['PUT'])
@parse_repository_name
@anon_allowed
def put_repository_auth(namespace, repository):
abort(501, 'Not Implemented', issue='not-implemented')
@ -353,11 +360,13 @@ def get_search():
# Note: This is *not* part of the Docker index spec. This is here for our own health check,
# since we have nginx handle the _ping below.
@index.route('/_internal_ping')
@anon_allowed
def internal_ping():
return make_response('true', 200)
@index.route('/_ping')
@index.route('/_ping')
@anon_allowed
def ping():
# NOTE: any changes made here must also be reflected in the nginx config
response = make_response('true', 200)

View file

@ -174,6 +174,7 @@ def get_image_layer(namespace, repository, image_id, headers):
@registry.route('/images/<image_id>/layer', methods=['PUT'])
@process_auth
@extract_namespace_repo_from_session
@anon_protect
def put_image_layer(namespace, repository, image_id):
logger.debug('Checking repo permissions')
permission = ModifyRepositoryPermission(namespace, repository)
@ -279,6 +280,7 @@ def put_image_layer(namespace, repository, image_id):
@registry.route('/images/<image_id>/checksum', methods=['PUT'])
@process_auth
@extract_namespace_repo_from_session
@anon_protect
def put_image_checksum(namespace, repository, image_id):
logger.debug('Checking repo permissions')
permission = ModifyRepositoryPermission(namespace, repository)
@ -439,6 +441,7 @@ def store_checksum(image_storage, checksum):
@registry.route('/images/<image_id>/json', methods=['PUT'])
@process_auth
@extract_namespace_repo_from_session
@anon_protect
def put_image_json(namespace, repository, image_id):
logger.debug('Checking repo permissions')
permission = ModifyRepositoryPermission(namespace, repository)

View file

@ -54,6 +54,7 @@ def get_tag(namespace, repository, tag):
@tags.route('/repositories/<path:repository>/tags/<tag>',
methods=['PUT'])
@process_auth
@anon_protect
@parse_repository_name
def put_tag(namespace, repository, tag):
permission = ModifyRepositoryPermission(namespace, repository)
@ -76,6 +77,7 @@ def put_tag(namespace, repository, tag):
@tags.route('/repositories/<path:repository>/tags/<tag>',
methods=['DELETE'])
@process_auth
@anon_protect
@parse_repository_name
def delete_tag(namespace, repository, tag):
permission = ModifyRepositoryPermission(namespace, repository)

33
test/test_anon_checked.py Normal file
View file

@ -0,0 +1,33 @@
import unittest
from endpoints.tags import tags
from endpoints.registry import registry
from endpoints.index import index
from endpoints.verbs import verbs
class TestAnonymousAccessChecked(unittest.TestCase):
def verifyBlueprint(self, blueprint):
class Checker(object):
def __init__(self, test_case):
self.test_case = test_case
def add_url_rule(self, rule, endpoint, view_function, methods=None):
if (not '__anon_protected' in dir(view_function) and
not '__anon_allowed' in dir(view_function)):
error_message = ('Missing anonymous access protection decorator on function ' +
'%s under blueprint %s' % (endpoint, blueprint.name))
self.test_case.fail(error_message)
for deferred_function in blueprint.deferred_functions:
deferred_function(Checker(self))
def test_anonymous_access_checked(self):
self.verifyBlueprint(tags)
self.verifyBlueprint(registry)
self.verifyBlueprint(index)
self.verifyBlueprint(verbs)
if __name__ == '__main__':
unittest.main()