diff --git a/auth/registry_jwt_auth.py b/auth/registry_jwt_auth.py index 42ca5329b..38c10f7ad 100644 --- a/auth/registry_jwt_auth.py +++ b/auth/registry_jwt_auth.py @@ -1,8 +1,9 @@ import logging import re -from jsonschema import validate, ValidationError from functools import wraps + +from jsonschema import validate, ValidationError from flask import request, url_for from flask.ext.principal import identity_changed, Identity from cryptography.x509 import load_pem_x509_certificate diff --git a/endpoints/api/trigger.py b/endpoints/api/trigger.py index b986c591b..0acf676ac 100644 --- a/endpoints/api/trigger.py +++ b/endpoints/api/trigger.py @@ -3,10 +3,11 @@ import json import logging -from flask import request, url_for from urllib import quote from urlparse import urlunparse +from flask import request, url_for + from app import app from buildtrigger.basehandler import BuildTriggerHandler from buildtrigger.triggerutil import (TriggerDeactivationException, @@ -40,9 +41,9 @@ class BuildTriggerList(RepositoryParamResource): @require_repo_admin @nickname('listBuildTriggers') - def get(self, namespace, repository): + def get(self, namespace_name, repo_name): """ List the triggers for the specified repository. """ - triggers = model.build.list_build_triggers(namespace, repository) + triggers = model.build.list_build_triggers(namespace_name, repo_name) return { 'triggers': [trigger_view(trigger, can_admin=True) for trigger in triggers] } @@ -56,7 +57,7 @@ class BuildTrigger(RepositoryParamResource): @require_repo_admin @nickname('getBuildTrigger') - def get(self, namespace, repository, trigger_uuid): + def get(self, namespace_name, repo_name, trigger_uuid): """ Get information for the specified build trigger. """ try: trigger = model.build.get_build_trigger(trigger_uuid) @@ -67,7 +68,7 @@ class BuildTrigger(RepositoryParamResource): @require_repo_admin @nickname('deleteBuildTrigger') - def delete(self, namespace, repository, trigger_uuid): + def delete(self, namespace_name, repo_name, trigger_uuid): """ Delete the specified build trigger. """ try: trigger = model.build.get_build_trigger(trigger_uuid) @@ -82,10 +83,10 @@ class BuildTrigger(RepositoryParamResource): # We are just going to eat this error logger.warning('Trigger deactivation problem: %s', ex) - log_action('delete_repo_trigger', namespace, - {'repo': repository, 'trigger_id': trigger_uuid, + log_action('delete_repo_trigger', namespace_name, + {'repo': repo_name, 'trigger_id': trigger_uuid, 'service': trigger.service.name}, - repo=model.repository.get_repository(namespace, repository)) + repo=model.repository.get_repository(namespace_name, repo_name)) trigger.delete_instance(recursive=True) @@ -111,7 +112,7 @@ class BuildTriggerSubdirs(RepositoryParamResource): @require_repo_admin @nickname('listBuildTriggerSubdirs') @validate_json_request('BuildTriggerSubdirRequest') - def post(self, namespace, repository, trigger_uuid): + def post(self, namespace_name, repo_name, trigger_uuid): """ List the subdirectories available for the specified build trigger and source. """ try: trigger = model.build.get_build_trigger(trigger_uuid) @@ -171,7 +172,7 @@ class BuildTriggerActivate(RepositoryParamResource): @require_repo_admin @nickname('activateBuildTrigger') @validate_json_request('BuildTriggerActivateRequest') - def post(self, namespace, repository, trigger_uuid): + def post(self, namespace_name, repo_name, trigger_uuid): """ Activate the specified build trigger. """ try: trigger = model.build.get_build_trigger(trigger_uuid) @@ -198,7 +199,7 @@ class BuildTriggerActivate(RepositoryParamResource): raise Unauthorized() # Make sure the namespace matches that of the trigger. - if robot_namespace != namespace: + if robot_namespace != namespace_name: raise Unauthorized() # Set the pull robot. @@ -208,7 +209,7 @@ class BuildTriggerActivate(RepositoryParamResource): new_config_dict = request.get_json()['config'] write_token_name = 'Build Trigger: %s' % trigger.service.name - write_token = model.token.create_delegate_token(namespace, repository, write_token_name, + write_token = model.token.create_delegate_token(namespace_name, repo_name, write_token_name, 'write') try: @@ -233,12 +234,13 @@ class BuildTriggerActivate(RepositoryParamResource): trigger.save() # Log the trigger setup. - repo = model.repository.get_repository(namespace, repository) - log_action('setup_repo_trigger', namespace, - {'repo': repository, 'namespace': namespace, - 'trigger_id': trigger.uuid, 'service': trigger.service.name, - 'pull_robot': trigger.pull_robot.username if trigger.pull_robot else None, - 'config': final_config}, repo=repo) + repo = model.repository.get_repository(namespace_name, repo_name) + log_action('setup_repo_trigger', namespace_name, + {'repo': repo_name, 'namespace': namespace_name, + 'trigger_id': trigger.uuid, 'service': trigger.service.name, + 'pull_robot': trigger.pull_robot.username if trigger.pull_robot else None, + 'config': final_config}, + repo=repo) return trigger_view(trigger, can_admin=True) else: @@ -271,7 +273,7 @@ class BuildTriggerAnalyze(RepositoryParamResource): @require_repo_admin @nickname('analyzeBuildTrigger') @validate_json_request('BuildTriggerAnalyzeRequest') - def post(self, namespace, repository, trigger_uuid): + def post(self, namespace_name, repo_name, trigger_uuid): """ Analyze the specified build trigger configuration. """ try: trigger = model.build.get_build_trigger(trigger_uuid) @@ -414,7 +416,7 @@ class ActivateBuildTrigger(RepositoryParamResource): @require_repo_admin @nickname('manuallyStartBuildTrigger') @validate_json_request('RunParameters') - def post(self, namespace, repository, trigger_uuid): + def post(self, namespace_name, repo_name, trigger_uuid): """ Manually start a build from the specified trigger. """ try: trigger = model.build.get_build_trigger(trigger_uuid) @@ -426,7 +428,7 @@ class ActivateBuildTrigger(RepositoryParamResource): raise InvalidRequest('Trigger is not active.') try: - repo = model.repository.get_repository(namespace, repository) + repo = model.repository.get_repository(namespace_name, repo_name) pull_robot_name = model.build.get_pull_robot_name(trigger) run_parameters = request.get_json() @@ -436,7 +438,7 @@ class ActivateBuildTrigger(RepositoryParamResource): raise InvalidRequest(tse.message) resp = build_status_view(build_request) - repo_string = '%s/%s' % (namespace, repository) + repo_string = '%s/%s' % (namespace_name, repo_name) headers = { 'Location': api.url_for(RepositoryBuildStatus, repository=repo_string, build_uuid=build_request.uuid), @@ -453,10 +455,10 @@ class TriggerBuildList(RepositoryParamResource): @parse_args() @query_param('limit', 'The maximum number of builds to return', type=int, default=5) @nickname('listTriggerRecentBuilds') - def get(self, namespace, repository, trigger_uuid, parsed_args): + def get(self, namespace_name, repo_name, trigger_uuid, parsed_args): """ List the builds started by the specified trigger. """ limit = parsed_args['limit'] - builds = model.build.list_trigger_builds(namespace, repository, trigger_uuid, limit) + builds = model.build.list_trigger_builds(namespace_name, repo_name, trigger_uuid, limit) return { 'builds': [build_status_view(bld) for bld in builds] } @@ -470,7 +472,7 @@ class BuildTriggerFieldValues(RepositoryParamResource): """ Custom verb to fetch a values list for a particular field name. """ @require_repo_admin @nickname('listTriggerFieldValues') - def post(self, namespace, repository, trigger_uuid, field_name): + def post(self, namespace_name, repo_name, trigger_uuid, field_name): """ List the field values for a custom run field. """ try: trigger = model.build.get_build_trigger(trigger_uuid) @@ -501,7 +503,7 @@ class BuildTriggerSources(RepositoryParamResource): """ Custom verb to fetch the list of build sources for the trigger config. """ @require_repo_admin @nickname('listTriggerBuildSources') - def get(self, namespace, repository, trigger_uuid): + def get(self, namespace_name, repo_name, trigger_uuid): """ List the build sources for the trigger configuration thus far. """ try: trigger = model.build.get_build_trigger(trigger_uuid) diff --git a/endpoints/common.py b/endpoints/common.py index e3e37aa07..c683fab55 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -47,24 +47,28 @@ def get_cache_busters(): return CACHE_BUSTERS -def parse_repository_name(f): - @wraps(f) - def wrapper(repository, *args, **kwargs): - lib_namespace = app.config['LIBRARY_NAMESPACE'] - (namespace, repository) = parse_namespace_repository(repository, lib_namespace) - return f(namespace, repository, *args, **kwargs) - return wrapper +def parse_repository_name(include_tag=False, + ns_kwarg_name='namespace_name', + repo_kwarg_name='repo_name', + tag_kwarg_name='tag_name', + incoming_repo_kwarg='repository'): + def inner(func): + @wraps(func) + def wrapper(*args, **kwargs): + parsed_stuff = parse_namespace_repository(kwargs[incoming_repo_kwarg], + app.config['LIBRARY_NAMESPACE'], + include_tag=include_tag) + del kwargs[incoming_repo_kwarg] + kwargs[ns_kwarg_name] = parsed_stuff[0] + kwargs[repo_kwarg_name] = parsed_stuff[1] + if include_tag: + kwargs[tag_kwarg_name] = parsed_stuff[2] + return func(*args, **kwargs) + return wrapper + return inner -def parse_repository_name_and_tag(f): - @wraps(f) - def wrapper(repository, *args, **kwargs): - lib_namespace = app.config['LIBRARY_NAMESPACE'] - namespace, repository, tag = parse_namespace_repository(repository, lib_namespace, - include_tag=True) - return f(namespace, repository, tag, *args, **kwargs) - return wrapper - +# TODO get rid of all calls to this parse_repository_name_and_tag def route_show_if(value): def decorator(f): diff --git a/endpoints/githubtrigger.py b/endpoints/githubtrigger.py index 5e2553d22..89c5f2841 100644 --- a/endpoints/githubtrigger.py +++ b/endpoints/githubtrigger.py @@ -3,39 +3,41 @@ import logging from flask import request, redirect, url_for, Blueprint from flask.ext.login import current_user -from endpoints.common import route_show_if, parse_repository_name -from app import app, github_trigger -from data import model -from util.http import abort -from auth.permissions import AdministerRepositoryPermission -from auth.auth import require_session_login - import features +from app import app, github_trigger +from auth.auth import require_session_login +from auth.permissions import AdministerRepositoryPermission +from data import model +from endpoints.common import route_show_if, parse_repository_name +from util.http import abort + + logger = logging.getLogger(__name__) client = app.config['HTTPCLIENT'] githubtrigger = Blueprint('callback', __name__) + @githubtrigger.route('/github/callback/trigger/', methods=['GET']) @route_show_if(features.GITHUB_BUILD) @require_session_login -@parse_repository_name -def attach_github_build_trigger(namespace, repository): - permission = AdministerRepositoryPermission(namespace, repository) +@parse_repository_name() +def attach_github_build_trigger(namespace_name, repo_name): + permission = AdministerRepositoryPermission(namespace_name, repo_name) if permission.can(): code = request.args.get('code') token = github_trigger.exchange_code_for_token(app.config, client, code) - repo = model.repository.get_repository(namespace, repository) + repo = model.repository.get_repository(namespace_name, repo_name) if not repo: - msg = 'Invalid repository: %s/%s' % (namespace, repository) + msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name) abort(404, message=msg) trigger = model.build.create_build_trigger(repo, 'github', token, current_user.db_user()) - repo_path = '%s/%s' % (namespace, repository) + repo_path = '%s/%s' % (namespace_name, repo_name) full_url = '%s%s%s' % (url_for('web.repository', path=repo_path), '?tab=builds&newtrigger=', trigger.uuid) logger.debug('Redirecting to full url: %s', full_url) return redirect(full_url) - abort(403) \ No newline at end of file + abort(403) diff --git a/endpoints/oauthlogin.py b/endpoints/oauthlogin.py index 9c99836b6..085134743 100644 --- a/endpoints/oauthlogin.py +++ b/endpoints/oauthlogin.py @@ -3,18 +3,18 @@ import requests from flask import request, redirect, url_for, Blueprint from flask.ext.login import current_user - -from endpoints.common import common_login, route_show_if, parse_repository_name -from endpoints.web import render_page_template_with_routedata -from app import app, analytics, get_app_url, github_login, google_login, dex_login -from data import model -from util.validation import generate_valid_usernames -from util.http import abort -from auth.auth import require_session_login from peewee import IntegrityError import features + +from app import app, analytics, get_app_url, github_login, google_login, dex_login +from auth.auth import require_session_login +from data import model +from endpoints.common import common_login, route_show_if +from endpoints.web import render_page_template_with_routedata from util.security.strictjwt import decode, InvalidTokenError +from util.validation import generate_valid_usernames + logger = logging.getLogger(__name__) client = app.config['HTTPCLIENT'] @@ -24,10 +24,10 @@ def render_ologin_error(service_name, error_message='Could not load user data. The token may have expired.'): user_creation = features.USER_CREATION and features.DIRECT_LOGIN return render_page_template_with_routedata('ologinerror.html', - service_name=service_name, - error_message=error_message, - service_url=get_app_url(), - user_creation=user_creation) + service_name=service_name, + error_message=error_message, + service_url=get_app_url(), + user_creation=user_creation) def get_user(service, token): @@ -150,7 +150,7 @@ def github_oauth_callback(): # Retrieve the user's orgnizations (if organization filtering is turned on) if github_login.allowed_organizations() is not None: get_orgs = client.get(github_login.orgs_endpoint(), params=token_param, - headers={'Accept': 'application/vnd.github.moondragon+json'}) + headers={'Accept': 'application/vnd.github.moondragon+json'}) organizations = set([org.get('login').lower() for org in get_orgs.json()]) if not (organizations & set(github_login.allowed_organizations())): @@ -271,8 +271,10 @@ def dex_oauth_callback(): payload = decode_user_jwt(token, dex_login) except InvalidTokenError: logger.exception('Exception when decoding returned JWT') - return render_ologin_error(dex_login.public_title, - 'Could not decode response. Please contact your system administrator about this error.') + return render_ologin_error( + dex_login.public_title, + 'Could not decode response. Please contact your system administrator about this error.', + ) username = get_email_username(payload) metadata = {} @@ -281,9 +283,11 @@ def dex_oauth_callback(): email_address = payload['email'] if not payload.get('email_verified', False): - return render_ologin_error(dex_login.public_title, + return render_ologin_error( + dex_login.public_title, 'A verified e-mail address is required for login. Please verify your ' + - 'e-mail address in %s and try again.' % dex_login.public_title) + 'e-mail address in %s and try again.' % dex_login.public_title, + ) return conduct_oauth_login(dex_login, dex_id, username, email_address, @@ -304,8 +308,10 @@ def dex_oauth_attach(): payload = decode_user_jwt(token, dex_login) except jwt.InvalidTokenError: logger.exception('Exception when decoding returned JWT') - return render_ologin_error(dex_login.public_title, - 'Could not decode response. Please contact your system administrator about this error.') + return render_ologin_error( + dex_login.public_title, + 'Could not decode response. Please contact your system administrator about this error.', + ) user_obj = current_user.db_user() dex_id = payload['sub'] @@ -315,7 +321,7 @@ def dex_oauth_attach(): model.user.attach_federated_login(user_obj, 'dex', dex_id, metadata=metadata) except IntegrityError: err = '%s account is already attached to a %s account' % (dex_login.public_title, - app.config['REGISTRY_TITLE_SHORT']) + app.config['REGISTRY_TITLE_SHORT']) return render_ologin_error(dex_login.public_title, err) return redirect(url_for('web.user')) diff --git a/endpoints/v1/index.py b/endpoints/v1/index.py index 517bc9f1c..2440cf11a 100644 --- a/endpoints/v1/index.py +++ b/endpoints/v1/index.py @@ -2,19 +2,19 @@ import json import logging import urlparse -from flask import request, make_response, jsonify, session from functools import wraps +from flask import request, make_response, jsonify, session + from data import model -from app import app, authentication, userevents, storage +from app import authentication, userevents from auth.auth import process_auth, generate_signed_token from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token -from util.names import REPOSITORY_NAME_REGEX from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission, ReadRepositoryPermission, CreateRepositoryPermission, repository_read_grant, repository_write_grant) - from util.http import abort +from util.names import REPOSITORY_NAME_REGEX from endpoints.common import parse_repository_name from endpoints.v1 import v1_bp from endpoints.trackhelper import track_and_log @@ -33,12 +33,12 @@ class GrantType(object): def generate_headers(scope=GrantType.READ_REPOSITORY, add_grant_for_status=None): def decorator_method(f): @wraps(f) - def wrapper(namespace, repository, *args, **kwargs): - response = f(namespace, repository, *args, **kwargs) + def wrapper(namespace_name, repo_name, *args, **kwargs): + response = f(namespace_name, repo_name, *args, **kwargs) # Setting session namespace and repository - session['namespace'] = namespace - session['repository'] = repository + session['namespace'] = namespace_name + session['repository'] = repo_name # We run our index and registry on the same hosts for now registry_server = urlparse.urlparse(request.url).netloc @@ -51,11 +51,11 @@ def generate_headers(scope=GrantType.READ_REPOSITORY, add_grant_for_status=None) grants = [] if scope == GrantType.READ_REPOSITORY: - if force_grant or ReadRepositoryPermission(namespace, repository).can(): - grants.append(repository_read_grant(namespace, repository)) + if force_grant or ReadRepositoryPermission(namespace_name, repo_name).can(): + grants.append(repository_read_grant(namespace_name, repo_name)) elif scope == GrantType.WRITE_REPOSITORY: - if force_grant or ModifyRepositoryPermission(namespace, repository).can(): - grants.append(repository_write_grant(namespace, repository)) + if force_grant or ModifyRepositoryPermission(namespace_name, repo_name).can(): + grants.append(repository_write_grant(namespace_name, repo_name)) # Generate a signed token for the user (if any) and the grants (if any) if grants or get_authenticated_user(): @@ -170,50 +170,50 @@ def update_user(username): @v1_bp.route('/repositories//', methods=['PUT']) @process_auth -@parse_repository_name +@parse_repository_name() @generate_headers(scope=GrantType.WRITE_REPOSITORY, add_grant_for_status=201) @anon_allowed -def create_repository(namespace, repository): +def create_repository(namespace_name, repo_name): # Verify that the repository name is valid. - if not REPOSITORY_NAME_REGEX.match(repository): + if not REPOSITORY_NAME_REGEX.match(repo_name): abort(400, message='Invalid repository name. Repository names cannot contain slashes.') - logger.debug('Looking up repository %s/%s', namespace, repository) - repo = model.repository.get_repository(namespace, repository) + logger.debug('Looking up repository %s/%s', namespace_name, repo_name) + repo = model.repository.get_repository(namespace_name, repo_name) - logger.debug('Found repository %s/%s', namespace, repository) + logger.debug('Found repository %s/%s', namespace_name, repo_name) if not repo and get_authenticated_user() is None: - logger.debug('Attempt to create repository %s/%s without user auth', namespace, repository) + logger.debug('Attempt to create repository %s/%s without user auth', namespace_name, repo_name) abort(401, message='Cannot create a repository as a guest. Please login via "docker login" first.', issue='no-login') elif repo: - permission = ModifyRepositoryPermission(namespace, repository) + permission = ModifyRepositoryPermission(namespace_name, repo_name) if not permission.can(): abort(403, message='You do not have permission to modify repository %(namespace)s/%(repository)s', issue='no-repo-write-permission', - namespace=namespace, repository=repository) + namespace=namespace_name, repository=repo_name) else: - permission = CreateRepositoryPermission(namespace) + permission = CreateRepositoryPermission(namespace_name) if not permission.can(): - logger.info('Attempt to create a new repo %s/%s with insufficient perms', namespace, - repository) + logger.info('Attempt to create a new repo %s/%s with insufficient perms', namespace_name, + repo_name) msg = 'You do not have permission to create repositories in namespace "%(namespace)s"' - abort(403, message=msg, issue='no-create-permission', namespace=namespace) + abort(403, message=msg, issue='no-create-permission', namespace=namespace_name) # Attempt to create the new repository. - logger.debug('Creating repository %s/%s with owner: %s', namespace, repository, + logger.debug('Creating repository %s/%s with owner: %s', namespace_name, repo_name, get_authenticated_user().username) - repo = model.repository.create_repository(namespace, repository, get_authenticated_user()) + repo = model.repository.create_repository(namespace_name, repo_name, get_authenticated_user()) if get_authenticated_user(): user_event_data = { 'action': 'push_start', - 'repository': repository, - 'namespace': namespace + 'repository': repo_name, + 'namespace': namespace_name, } event = userevents.get_event(get_authenticated_user().username) @@ -224,15 +224,15 @@ def create_repository(namespace, repository): @v1_bp.route('/repositories//images', methods=['PUT']) @process_auth -@parse_repository_name +@parse_repository_name() @generate_headers(scope=GrantType.WRITE_REPOSITORY) @anon_allowed -def update_images(namespace, repository): - permission = ModifyRepositoryPermission(namespace, repository) +def update_images(namespace_name, repo_name): + permission = ModifyRepositoryPermission(namespace_name, repo_name) if permission.can(): logger.debug('Looking up repository') - repo = model.repository.get_repository(namespace, repository) + repo = model.repository.get_repository(namespace_name, repo_name) if not repo: # Make sure the repo actually exists. abort(404, message='Unknown repository', issue='unknown-repo') @@ -254,17 +254,17 @@ def update_images(namespace, repository): @v1_bp.route('/repositories//images', methods=['GET']) @process_auth -@parse_repository_name +@parse_repository_name() @generate_headers(scope=GrantType.READ_REPOSITORY) @anon_protect -def get_repository_images(namespace, repository): - permission = ReadRepositoryPermission(namespace, repository) +def get_repository_images(namespace_name, repo_name): + permission = ReadRepositoryPermission(namespace_name, repo_name) # TODO invalidate token? - if permission.can() or model.repository.repository_is_public(namespace, repository): + if permission.can() or model.repository.repository_is_public(namespace_name, repo_name): # We can't rely on permissions to tell us if a repo exists anymore logger.debug('Looking up repository') - repo = model.repository.get_repository(namespace, repository) + repo = model.repository.get_repository(namespace_name, repo_name) if not repo: abort(404, message='Unknown repository', issue='unknown-repo') @@ -280,17 +280,17 @@ def get_repository_images(namespace, repository): @v1_bp.route('/repositories//images', methods=['DELETE']) @process_auth -@parse_repository_name +@parse_repository_name() @generate_headers(scope=GrantType.WRITE_REPOSITORY) @anon_allowed -def delete_repository_images(namespace, repository): +def delete_repository_images(namespace_name, repo_name): abort(501, 'Not Implemented', issue='not-implemented') @v1_bp.route('/repositories//auth', methods=['PUT']) -@parse_repository_name +@parse_repository_name() @anon_allowed -def put_repository_auth(namespace, repository): +def put_repository_auth(namespace_name, repo_name): abort(501, 'Not Implemented', issue='not-implemented') diff --git a/endpoints/v1/tag.py b/endpoints/v1/tag.py index d9fd509e5..da730748d 100644 --- a/endpoints/v1/tag.py +++ b/endpoints/v1/tag.py @@ -1,10 +1,8 @@ - import logging import json from flask import abort, request, jsonify, make_response, session -from app import app from util.names import TAG_ERROR, TAG_REGEX from auth.auth import process_auth from auth.permissions import (ReadRepositoryPermission, @@ -22,12 +20,12 @@ logger = logging.getLogger(__name__) @v1_bp.route('/repositories//tags', methods=['GET']) @process_auth @anon_protect -@parse_repository_name -def get_tags(namespace, repository): - permission = ReadRepositoryPermission(namespace, repository) +@parse_repository_name() +def get_tags(namespace_name, repo_name): + permission = ReadRepositoryPermission(namespace_name, repo_name) - if permission.can() or model.repository.repository_is_public(namespace, repository): - tags = model.tag.list_repository_tags(namespace, repository) + if permission.can() or model.repository.repository_is_public(namespace_name, repo_name): + tags = model.tag.list_repository_tags(namespace_name, repo_name) tag_map = {tag.name: tag.image.docker_image_id for tag in tags} return jsonify(tag_map) @@ -37,13 +35,13 @@ def get_tags(namespace, repository): @v1_bp.route('/repositories//tags/', methods=['GET']) @process_auth @anon_protect -@parse_repository_name -def get_tag(namespace, repository, tag): - permission = ReadRepositoryPermission(namespace, repository) +@parse_repository_name() +def get_tag(namespace_name, repo_name, tag): + permission = ReadRepositoryPermission(namespace_name, repo_name) - if permission.can() or model.repository.repository_is_public(namespace, repository): + if permission.can() or model.repository.repository_is_public(namespace_name, repo_name): try: - tag_image = model.tag.get_tag_image(namespace, repository, tag) + tag_image = model.tag.get_tag_image(namespace_name, repo_name, tag) except model.DataModelException: abort(404) @@ -57,19 +55,19 @@ def get_tag(namespace, repository, tag): @v1_bp.route('/repositories//tags/', methods=['PUT']) @process_auth @anon_protect -@parse_repository_name -def put_tag(namespace, repository, tag): - permission = ModifyRepositoryPermission(namespace, repository) +@parse_repository_name() +def put_tag(namespace_name, repo_name, tag): + permission = ModifyRepositoryPermission(namespace_name, repo_name) if permission.can(): if not TAG_REGEX.match(tag): abort(400, TAG_ERROR) docker_image_id = json.loads(request.data) - model.tag.create_or_update_tag(namespace, repository, tag, docker_image_id) + model.tag.create_or_update_tag(namespace_name, repo_name, tag, docker_image_id) # Store the updated tag. - if not 'pushed_tags' in session: + if 'pushed_tags' not in session: session['pushed_tags'] = {} session['pushed_tags'][tag] = docker_image_id @@ -82,13 +80,13 @@ def put_tag(namespace, repository, tag): @v1_bp.route('/repositories//tags/', methods=['DELETE']) @process_auth @anon_protect -@parse_repository_name -def delete_tag(namespace, repository, tag): - permission = ModifyRepositoryPermission(namespace, repository) +@parse_repository_name() +def delete_tag(namespace_name, repo_name, tag): + permission = ModifyRepositoryPermission(namespace_name, repo_name) if permission.can(): - model.tag.delete_tag(namespace, repository, tag) - track_and_log('delete_tag', model.repository.get_repository(namespace, repository), + model.tag.delete_tag(namespace_name, repo_name, tag) + track_and_log('delete_tag', model.repository.get_repository(namespace_name, repo_name), tag=tag) return make_response('Deleted', 200) diff --git a/endpoints/v2/__init__.py b/endpoints/v2/__init__.py index b02b1e541..10a69be9d 100644 --- a/endpoints/v2/__init__.py +++ b/endpoints/v2/__init__.py @@ -41,13 +41,14 @@ def handle_registry_v2_exception(error): def _require_repo_permission(permission_class, allow_public=False): def wrapper(func): @wraps(func) - def wrapped(namespace, repo_name, *args, **kwargs): - logger.debug('Checking permission %s for repo: %s/%s', permission_class, namespace, repo_name) - permission = permission_class(namespace, repo_name) + def wrapped(namespace_name, repo_name, *args, **kwargs): + logger.debug('Checking permission %s for repo: %s/%s', permission_class, + namespace_name, repo_name) + permission = permission_class(namespace_name, repo_name) if (permission.can() or (allow_public and - model.repository.repository_is_public(namespace, repo_name))): - return func(namespace, repo_name, *args, **kwargs) + model.repository.repository_is_public(namespace_name, repo_name))): + return func(namespace_name, repo_name, *args, **kwargs) raise Unauthorized() return wrapped return wrapper diff --git a/endpoints/v2/blob.py b/endpoints/v2/blob.py index 9b314d18a..07182a1d4 100644 --- a/endpoints/v2/blob.py +++ b/endpoints/v2/blob.py @@ -9,6 +9,7 @@ from app import storage, app from auth.registry_jwt_auth import process_registry_jwt_auth from data import model, database from digest import digest_tools +from endpoints.common import parse_repository_name from endpoints.v2 import v2_bp, require_repo_read, require_repo_write, get_input_stream from endpoints.v2.errors import (BlobUnknown, BlobUploadInvalid, BlobUploadUnknown, Unsupported, NameUnknown) @@ -17,7 +18,6 @@ from util.cache import cache_control from util.registry.filelike import wrap_with_handler, StreamSlice from util.registry.gzipstream import calculate_size_handler from util.registry.torrent import PieceHasher -from endpoints.common import parse_repository_name from storage.basestorage import InvalidChunkException @@ -34,12 +34,12 @@ class _InvalidRangeHeader(Exception): pass -def _base_blob_fetch(namespace, repo_name, digest): +def _base_blob_fetch(namespace_name, repo_name, digest): """ Some work that is common to both GET and HEAD requests. Callers MUST check for proper authorization before calling this method. """ try: - found = model.blob.get_repo_blob_by_digest(namespace, repo_name, digest) + found = model.blob.get_repo_blob_by_digest(namespace_name, repo_name, digest) except model.BlobDoesNotExist: raise BlobUnknown() @@ -58,12 +58,12 @@ def _base_blob_fetch(namespace, repo_name, digest): @v2_bp.route(BLOB_DIGEST_ROUTE, methods=['HEAD']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_read @anon_protect @cache_control(max_age=31436000) -def check_blob_exists(namespace, repo_name, digest): - found, headers = _base_blob_fetch(namespace, repo_name, digest) +def check_blob_exists(namespace_name, repo_name, digest): + found, headers = _base_blob_fetch(namespace_name, repo_name, digest) response = make_response('') response.headers.extend(headers) @@ -74,12 +74,12 @@ def check_blob_exists(namespace, repo_name, digest): @v2_bp.route(BLOB_DIGEST_ROUTE, methods=['GET']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_read @anon_protect @cache_control(max_age=31536000) -def download_blob(namespace, repo_name, digest): - found, headers = _base_blob_fetch(namespace, repo_name, digest) +def download_blob(namespace_name, repo_name, digest): + found, headers = _base_blob_fetch(namespace_name, repo_name, digest) path = model.storage.get_layer_path(found) logger.debug('Looking up the direct download URL for path: %s', path) @@ -108,15 +108,15 @@ def _render_range(num_uploaded_bytes, with_bytes_prefix=True): @v2_bp.route('//blobs/uploads/', methods=['POST']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_write @anon_protect -def start_blob_upload(namespace, repo_name): +def start_blob_upload(namespace_name, repo_name): location_name = storage.preferred_locations[0] new_upload_uuid, upload_metadata = storage.initiate_chunked_upload(location_name) try: - model.blob.initiate_upload(namespace, repo_name, new_upload_uuid, location_name, + model.blob.initiate_upload(namespace_name, repo_name, new_upload_uuid, location_name, upload_metadata) except database.Repository.DoesNotExist: raise NameUnknown() @@ -126,7 +126,7 @@ def start_blob_upload(namespace, repo_name): # The user will send the blob data in another request accepted = make_response('', 202) accepted.headers['Location'] = url_for('v2.upload_chunk', - repository='%s/%s' % (namespace, repo_name), + repository='%s/%s' % (namespace_name, repo_name), upload_uuid=new_upload_uuid) accepted.headers['Range'] = _render_range(0) @@ -134,22 +134,22 @@ def start_blob_upload(namespace, repo_name): return accepted else: # The user plans to send us the entire body right now - uploaded, error = _upload_chunk(namespace, repo_name, new_upload_uuid) + uploaded, error = _upload_chunk(namespace_name, repo_name, new_upload_uuid) uploaded.save() if error: _range_not_satisfiable(uploaded.byte_count) - return _finish_upload(namespace, repo_name, uploaded, digest) + return _finish_upload(namespace_name, repo_name, uploaded, digest) @v2_bp.route('//blobs/uploads/', methods=['GET']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_write @anon_protect -def fetch_existing_upload(namespace, repo_name, upload_uuid): +def fetch_existing_upload(namespace_name, repo_name, upload_uuid): try: - found = model.blob.get_blob_upload(namespace, repo_name, upload_uuid) + found = model.blob.get_blob_upload(namespace_name, repo_name, upload_uuid) except model.InvalidBlobUpload: raise BlobUploadUnknown() @@ -189,12 +189,12 @@ def _parse_range_header(range_header_text): return (start, length) -def _upload_chunk(namespace, repo_name, upload_uuid): +def _upload_chunk(namespace_name, repo_name, upload_uuid): """ Common code among the various uploading paths for appending data to blobs. Callers MUST call .save() or .delete_instance() on the returned database object. """ try: - found = model.blob.get_blob_upload(namespace, repo_name, upload_uuid) + found = model.blob.get_blob_upload(namespace_name, repo_name, upload_uuid) except model.InvalidBlobUpload: raise BlobUploadUnknown() @@ -280,7 +280,7 @@ def _upload_chunk(namespace, repo_name, upload_uuid): return found, error -def _finish_upload(namespace, repo_name, upload_obj, expected_digest): +def _finish_upload(namespace_name, repo_name, upload_obj, expected_digest): # Verify that the digest's SHA matches that of the uploaded data. computed_digest = digest_tools.sha256_digest_from_hashlib(upload_obj.sha_state) if not digest_tools.digests_equal(computed_digest, expected_digest): @@ -303,7 +303,7 @@ def _finish_upload(namespace, repo_name, upload_obj, expected_digest): final_blob_location, upload_obj.storage_metadata) # Mark the blob as uploaded. - blob_storage = model.blob.store_blob_record_and_temp_link(namespace, repo_name, expected_digest, + blob_storage = model.blob.store_blob_record_and_temp_link(namespace_name, repo_name, expected_digest, upload_obj.location, upload_obj.byte_count, app.config['PUSH_TEMP_TAG_EXPIRATION_SEC'], @@ -319,18 +319,18 @@ def _finish_upload(namespace, repo_name, upload_obj, expected_digest): response = make_response('', 201) response.headers['Docker-Content-Digest'] = expected_digest response.headers['Location'] = url_for('v2.download_blob', - repository='%s/%s' % (namespace, repo_name), + repository='%s/%s' % (namespace_name, repo_name), digest=expected_digest) return response @v2_bp.route('//blobs/uploads/', methods=['PATCH']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_write @anon_protect -def upload_chunk(namespace, repo_name, upload_uuid): - upload, error = _upload_chunk(namespace, repo_name, upload_uuid) +def upload_chunk(namespace_name, repo_name, upload_uuid): + upload, error = _upload_chunk(namespace_name, repo_name, upload_uuid) upload.save() if error: @@ -345,31 +345,31 @@ def upload_chunk(namespace, repo_name, upload_uuid): @v2_bp.route('//blobs/uploads/', methods=['PUT']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_write @anon_protect -def monolithic_upload_or_last_chunk(namespace, repo_name, upload_uuid): +def monolithic_upload_or_last_chunk(namespace_name, repo_name, upload_uuid): digest = request.args.get('digest', None) if digest is None: raise BlobUploadInvalid() - found, error = _upload_chunk(namespace, repo_name, upload_uuid) + found, error = _upload_chunk(namespace_name, repo_name, upload_uuid) if error: found.save() _range_not_satisfiable(found.byte_count) - return _finish_upload(namespace, repo_name, found, digest) + return _finish_upload(namespace_name, repo_name, found, digest) @v2_bp.route('//blobs/uploads/', methods=['DELETE']) +@parse_repository_name() @process_registry_jwt_auth -@parse_repository_name @require_repo_write @anon_protect -def cancel_upload(namespace, repo_name, upload_uuid): +def cancel_upload(namespace_name, repo_name, upload_uuid): try: - found = model.blob.get_blob_upload(namespace, repo_name, upload_uuid) + found = model.blob.get_blob_upload(namespace_name, repo_name, upload_uuid) except model.InvalidBlobUpload: raise BlobUploadUnknown() @@ -384,11 +384,9 @@ def cancel_upload(namespace, repo_name, upload_uuid): @v2_bp.route('//blobs/', methods=['DELETE']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_write @anon_protect -def delete_digest(namespace, repo_name, upload_uuid): +def delete_digest(namespace_name, repo_name, upload_uuid): # We do not support deleting arbitrary digests, as they break repo images. raise Unsupported() - - diff --git a/endpoints/v2/manifest.py b/endpoints/v2/manifest.py index 6199b45c1..5fbc248f0 100644 --- a/endpoints/v2/manifest.py +++ b/endpoints/v2/manifest.py @@ -1,18 +1,22 @@ import logging -import jwt.utils import json -import features import hashlib -from peewee import IntegrityError -from flask import make_response, request, url_for from collections import namedtuple, OrderedDict -from jwkest.jws import SIGNER_ALGS, keyrep from datetime import datetime from functools import wraps +import jwt.utils + +from peewee import IntegrityError +from flask import make_response, request, url_for +from jwkest.jws import SIGNER_ALGS, keyrep + +import features + from app import docker_v2_signing_key, app from auth.registry_jwt_auth import process_registry_jwt_auth +from endpoints.common import parse_repository_name from endpoints.decorators import anon_protect from endpoints.v2 import v2_bp, require_repo_read, require_repo_write from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnknown, TagInvalid, @@ -22,7 +26,6 @@ from endpoints.notificationhelper import spawn_notification from digest import digest_tools from data import model from data.database import RepositoryTag -from endpoints.common import parse_repository_name logger = logging.getLogger(__name__) @@ -84,7 +87,8 @@ class SignedManifest(object): def _validate(self): for signature in self._signatures: - bytes_to_verify = '{0}.{1}'.format(signature['protected'], jwt.utils.base64url_encode(self.payload)) + bytes_to_verify = '{0}.{1}'.format(signature['protected'], + jwt.utils.base64url_encode(self.payload)) signer = SIGNER_ALGS[signature['header']['alg']] key = keyrep(signature['header']['jwk']) gk = key.get_key() @@ -163,9 +167,9 @@ class SignedManifest(object): class SignedManifestBuilder(object): """ Class which represents a manifest which is currently being built. """ - def __init__(self, namespace, repo_name, tag, architecture='amd64', schema_ver=1): - repo_name_key = '{0}/{1}'.format(namespace, repo_name) - if namespace == '': + def __init__(self, namespace_name, repo_name, tag, architecture='amd64', schema_ver=1): + repo_name_key = '{0}/{1}'.format(namespace_name, repo_name) + if namespace_name == '': repo_name_key = repo_name self._base_payload = { @@ -238,26 +242,26 @@ class SignedManifestBuilder(object): @v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=['GET']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_read @anon_protect -def fetch_manifest_by_tagname(namespace, repo_name, manifest_ref): +def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref): try: - manifest = model.tag.load_tag_manifest(namespace, repo_name, manifest_ref) + manifest = model.tag.load_tag_manifest(namespace_name, repo_name, manifest_ref) except model.InvalidManifestException: try: - model.tag.get_active_tag(namespace, repo_name, manifest_ref) + model.tag.get_active_tag(namespace_name, repo_name, manifest_ref) except RepositoryTag.DoesNotExist: - raise ManifestUnknown() + raise ManifestUnknown() try: - manifest = _generate_and_store_manifest(namespace, repo_name, manifest_ref) + manifest = _generate_and_store_manifest(namespace_name, repo_name, manifest_ref) except model.DataModelException: - logger.exception('Exception when generating manifest for %s/%s:%s', namespace, repo_name, + logger.exception('Exception when generating manifest for %s/%s:%s', namespace_name, repo_name, manifest_ref) raise ManifestUnknown() - repo = model.repository.get_repository(namespace, repo_name) + repo = model.repository.get_repository(namespace_name, repo_name) if repo is not None: track_and_log('pull_repo', repo, analytics_name='pull_repo_100x', analytics_sample=0.01) @@ -269,17 +273,17 @@ def fetch_manifest_by_tagname(namespace, repo_name, manifest_ref): @v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['GET']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_read @anon_protect -def fetch_manifest_by_digest(namespace, repo_name, manifest_ref): +def fetch_manifest_by_digest(namespace_name, repo_name, manifest_ref): try: - manifest = model.tag.load_manifest_by_digest(namespace, repo_name, manifest_ref) + manifest = model.tag.load_manifest_by_digest(namespace_name, repo_name, manifest_ref) except model.InvalidManifestException: # Without a tag name to reference, we can't make an attempt to generate the manifest raise ManifestUnknown() - repo = model.repository.get_repository(namespace, repo_name) + repo = model.repository.get_repository(namespace_name, repo_name) if repo is not None: track_and_log('pull_repo', repo) @@ -301,11 +305,11 @@ def _reject_manifest2_schema2(func): @v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=['PUT']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_write @anon_protect @_reject_manifest2_schema2 -def write_manifest_by_tagname(namespace, repo_name, manifest_ref): +def write_manifest_by_tagname(namespace_name, repo_name, manifest_ref): try: manifest = SignedManifest(request.data) except ValueError: @@ -314,16 +318,16 @@ def write_manifest_by_tagname(namespace, repo_name, manifest_ref): if manifest.tag != manifest_ref: raise TagInvalid() - return _write_manifest(namespace, repo_name, manifest) + return _write_manifest(namespace_name, repo_name, manifest) @v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['PUT']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_write @anon_protect @_reject_manifest2_schema2 -def write_manifest_by_digest(namespace, repo_name, manifest_ref): +def write_manifest_by_digest(namespace_name, repo_name, manifest_ref): try: manifest = SignedManifest(request.data) except ValueError: @@ -332,7 +336,7 @@ def write_manifest_by_digest(namespace, repo_name, manifest_ref): if manifest.digest != manifest_ref: raise ManifestInvalid(detail={'message': 'manifest digest mismatch'}) - return _write_manifest(namespace, repo_name, manifest) + return _write_manifest(namespace_name, repo_name, manifest) def _updated_v1_metadata(v1_metadata_json, updated_id_map): @@ -350,21 +354,21 @@ def _updated_v1_metadata(v1_metadata_json, updated_id_map): return json.dumps(parsed) -def _write_manifest(namespace, repo_name, manifest): +def _write_manifest(namespace_name, repo_name, manifest): # Ensure that the manifest is for this repository. If the manifest's namespace is empty, then # it is for the library namespace and we need an extra check. if (manifest.namespace == '' and features.LIBRARY_SUPPORT and - namespace == app.config['LIBRARY_NAMESPACE']): + namespace_name == app.config['LIBRARY_NAMESPACE']): # This is a library manifest. All good. pass - elif manifest.namespace != namespace: + elif manifest.namespace != namespace_name: raise NameInvalid() if manifest.repo_name != repo_name: raise NameInvalid() # Ensure that the repository exists. - repo = model.repository.get_repository(namespace, repo_name) + repo = model.repository.get_repository(namespace_name, repo_name) if repo is None: raise NameInvalid() @@ -411,8 +415,8 @@ def _write_manifest(namespace, repo_name, manifest): v1_metadata_str = mdata.v1_metadata_str.encode('utf-8') working_docker_id = hashlib.sha256(v1_metadata_str + '@' + digest_str).hexdigest() - logger.debug('Rewriting docker_id %s/%s %s -> %s', namespace, repo_name, v1_mdata.docker_id, - working_docker_id) + logger.debug('Rewriting docker_id %s/%s %s -> %s', namespace_name, repo_name, + v1_mdata.docker_id, working_docker_id) has_rewritten_ids = True # Store the new docker id in the map @@ -447,7 +451,7 @@ def _write_manifest(namespace, repo_name, manifest): # Store the manifest pointing to the tag. manifest_digest = manifest.digest leaf_layer_id = images_map[layers[-1].v1_metadata.docker_id].docker_image_id - model.tag.store_tag_manifest(namespace, repo_name, tag_name, leaf_layer_id, manifest_digest, + model.tag.store_tag_manifest(namespace_name, repo_name, tag_name, leaf_layer_id, manifest_digest, manifest.bytes) # Spawn the repo_push event. @@ -461,29 +465,29 @@ def _write_manifest(namespace, repo_name, manifest): response = make_response('OK', 202) response.headers['Docker-Content-Digest'] = manifest_digest response.headers['Location'] = url_for('v2.fetch_manifest_by_digest', - repository='%s/%s' % (namespace, repo_name), + repository='%s/%s' % (namespace_name, repo_name), manifest_ref=manifest_digest) return response @v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['DELETE']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_write @anon_protect -def delete_manifest_by_digest(namespace, repo_name, manifest_ref): +def delete_manifest_by_digest(namespace_name, repo_name, manifest_ref): """ Delete the manifest specified by the digest. Note: there is no equivalent method for deleting by tag name because it is forbidden by the spec. """ try: - manifest = model.tag.load_manifest_by_digest(namespace, repo_name, manifest_ref) + manifest = model.tag.load_manifest_by_digest(namespace_name, repo_name, manifest_ref) except model.InvalidManifestException: # Without a tag name to reference, we can't make an attempt to generate the manifest raise ManifestUnknown() # Mark the tag as no longer alive. try: - model.tag.delete_tag(namespace, repo_name, manifest.tag.name) + model.tag.delete_tag(namespace_name, repo_name, manifest.tag.name) except model.DataModelException: # Tag is not alive. raise ManifestUnknown() @@ -494,15 +498,15 @@ def delete_manifest_by_digest(namespace, repo_name, manifest_ref): return make_response('', 202) -def _generate_and_store_manifest(namespace, repo_name, tag_name): +def _generate_and_store_manifest(namespace_name, repo_name, tag_name): # First look up the tag object and its ancestors - image = model.tag.get_tag_image(namespace, repo_name, tag_name) - parents = model.image.get_parent_images(namespace, repo_name, image) + image = model.tag.get_tag_image(namespace_name, repo_name, tag_name) + parents = model.image.get_parent_images(namespace_name, repo_name, image) # If the manifest is being generated under the library namespace, then we make its namespace # empty. - manifest_namespace = namespace - if features.LIBRARY_SUPPORT and namespace == app.config['LIBRARY_NAMESPACE']: + manifest_namespace = namespace_name + if features.LIBRARY_SUPPORT and namespace_name == app.config['LIBRARY_NAMESPACE']: manifest_namespace = '' # Create and populate the manifest builder @@ -520,13 +524,12 @@ def _generate_and_store_manifest(namespace, repo_name, tag_name): # Write the manifest to the DB. If an existing manifest already exists, return the # one found. try: - return model.tag.associate_generated_tag_manifest(namespace, repo_name, tag_name, + return model.tag.associate_generated_tag_manifest(namespace_name, repo_name, tag_name, manifest.digest, manifest.bytes) except IntegrityError as ie: logger.debug('Got integrity error: %s', ie) try: - return model.tag.load_tag_manifest(namespace, repo_name, tag_name) + return model.tag.load_tag_manifest(namespace_name, repo_name, tag_name) except model.InvalidManifestException: logger.exception('Exception when generating manifest') raise model.DataModelException('Could not load or generate manifest') - diff --git a/endpoints/v2/tag.py b/endpoints/v2/tag.py index 03aa51198..19b046468 100644 --- a/endpoints/v2/tag.py +++ b/endpoints/v2/tag.py @@ -1,29 +1,29 @@ from flask import jsonify, url_for from auth.registry_jwt_auth import process_registry_jwt_auth +from endpoints.common import parse_repository_name from endpoints.v2 import v2_bp, require_repo_read from endpoints.v2.errors import NameUnknown from endpoints.v2.v2util import add_pagination from endpoints.decorators import anon_protect from data import model -from endpoints.common import parse_repository_name @v2_bp.route('//tags/list', methods=['GET']) @process_registry_jwt_auth -@parse_repository_name +@parse_repository_name() @require_repo_read @anon_protect -def list_all_tags(namespace, repo_name): - repository = model.repository.get_repository(namespace, repo_name) +def list_all_tags(namespace_name, repo_name): + repository = model.repository.get_repository(namespace_name, repo_name) if repository is None: raise NameUnknown() - query = model.tag.list_repository_tags(namespace, repo_name) - url = url_for('v2.list_all_tags', repository='%s/%s' % (namespace, repo_name)) + query = model.tag.list_repository_tags(namespace_name, repo_name) + url = url_for('v2.list_all_tags', repository='%s/%s' % (namespace_name, repo_name)) link, query = add_pagination(query, url) response = jsonify({ - 'name': '{0}/{1}'.format(namespace, repo_name), + 'name': '{0}/{1}'.format(namespace_name, repo_name), 'tags': [tag.name for tag in query], }) diff --git a/endpoints/verbs.py b/endpoints/verbs.py index c89febc2b..1d7a8210b 100644 --- a/endpoints/verbs.py +++ b/endpoints/verbs.py @@ -381,10 +381,10 @@ def get_squashed_tag(namespace, repository, tag): @anon_protect @verbs.route('/torrent{0}'.format(BLOB_DIGEST_ROUTE), methods=['GET']) @process_auth -@parse_repository_name -def get_tag_torrent(namespace, repo_name, digest): - permission = ReadRepositoryPermission(namespace, repo_name) - public_repo = model.repository.repository_is_public(namespace, repo_name) +@parse_repository_name() +def get_tag_torrent(namespace_name, repo_name, digest): + permission = ReadRepositoryPermission(namespace_name, repo_name) + public_repo = model.repository.repository_is_public(namespace_name, repo_name) if not permission.can() and not public_repo: abort(403) @@ -394,7 +394,7 @@ def get_tag_torrent(namespace, repo_name, digest): abort(403) try: - blob = model.blob.get_repo_blob_by_digest(namespace, repo_name, digest) + blob = model.blob.get_repo_blob_by_digest(namespace_name, repo_name, digest) except model.BlobDoesNotExist: abort(404) diff --git a/endpoints/web.py b/endpoints/web.py index e05c4eb97..7147594c6 100644 --- a/endpoints/web.py +++ b/endpoints/web.py @@ -26,7 +26,7 @@ from data import model from data.database import db from endpoints.api.discovery import swagger_route_data from endpoints.common import (common_login, render_page_template, route_show_if, param_required, - parse_repository_name, parse_repository_name_and_tag) + parse_repository_name) from endpoints.csrf import csrf_protect, generate_csrf_token, verify_csrf from endpoints.decorators import anon_protect, anon_allowed from health.healthcheck import get_healthchecker @@ -411,20 +411,20 @@ def confirm_recovery(): @web.route('/repository//status', methods=['GET']) -@parse_repository_name +@parse_repository_name() @anon_protect -def build_status_badge(namespace, repository): +def build_status_badge(namespace_name, repo_name): token = request.args.get('token', None) - is_public = model.repository.repository_is_public(namespace, repository) + is_public = model.repository.repository_is_public(namespace_name, repo_name) if not is_public: - repo = model.repository.get_repository(namespace, repository) + repo = model.repository.get_repository(namespace_name, repo_name) if not repo or token != repo.badge_token: abort(404) # Lookup the tags for the repository. - tags = model.tag.list_repository_tags(namespace, repository) + tags = model.tag.list_repository_tags(namespace_name, repo_name) is_empty = len(list(tags)) == 0 - recent_build = model.build.get_recent_repository_build(namespace, repository) + recent_build = model.build.get_recent_repository_build(namespace_name, repo_name) if not is_empty and (not recent_build or recent_build.phase == 'complete'): status_name = 'ready' @@ -600,14 +600,14 @@ def download_logs_archive(): @web.route('/bitbucket/setup/', methods=['GET']) @require_session_login -@parse_repository_name +@parse_repository_name() @route_show_if(features.BITBUCKET_BUILD) -def attach_bitbucket_trigger(namespace, repository_name): - permission = AdministerRepositoryPermission(namespace, repository_name) +def attach_bitbucket_trigger(namespace_name, repo_name): + permission = AdministerRepositoryPermission(namespace_name, repo_name) if permission.can(): - repo = model.repository.get_repository(namespace, repository_name) + repo = model.repository.get_repository(namespace_name, repo_name) if not repo: - msg = 'Invalid repository: %s/%s' % (namespace, repository_name) + msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name) abort(404, message=msg) trigger = model.build.create_build_trigger(repo, BitbucketBuildTrigger.service_name(), None, @@ -634,19 +634,19 @@ def attach_bitbucket_trigger(namespace, repository_name): @web.route('/customtrigger/setup/', methods=['GET']) @require_session_login -@parse_repository_name -def attach_custom_build_trigger(namespace, repository_name): - permission = AdministerRepositoryPermission(namespace, repository_name) +@parse_repository_name() +def attach_custom_build_trigger(namespace_name, repo_name): + permission = AdministerRepositoryPermission(namespace_name, repo_name) if permission.can(): - repo = model.repository.get_repository(namespace, repository_name) + repo = model.repository.get_repository(namespace_name, repo_name) if not repo: - msg = 'Invalid repository: %s/%s' % (namespace, repository_name) + msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name) abort(404, message=msg) trigger = model.build.create_build_trigger(repo, CustomBuildTrigger.service_name(), None, current_user.db_user()) - repo_path = '%s/%s' % (namespace, repository_name) + repo_path = '%s/%s' % (namespace_name, repo_name) full_url = '%s%s%s' % (url_for('web.repository', path=repo_path), '?tab=builds&newtrigger=', trigger.uuid) @@ -655,21 +655,22 @@ def attach_custom_build_trigger(namespace, repository_name): abort(403) + @web.route('/') @no_cache @process_oauth -@parse_repository_name_and_tag +@parse_repository_name(include_tag=True) @anon_protect -def redirect_to_repository(namespace, reponame, tag_name): - permission = ReadRepositoryPermission(namespace, reponame) - is_public = model.repository.repository_is_public(namespace, reponame) +def redirect_to_repository(namespace_name, repo_name, tag_name): + permission = ReadRepositoryPermission(namespace_name, repo_name) + is_public = model.repository.repository_is_public(namespace_name, repo_name) if request.args.get('ac-discovery', 0) == 1: return index('') if permission.can() or is_public: - repository_name = '/'.join([namespace, reponame]) - return redirect(url_for('web.repository', path=repository_name, tag=tag_name)) + repo_path = '/'.join([namespace_name, repo_name]) + return redirect(url_for('web.repository', path=repo_path, tag=tag_name)) abort(404) diff --git a/local-test.sh b/local-test.sh index edce17a61..b7da1c3f3 100755 --- a/local-test.sh +++ b/local-test.sh @@ -5,4 +5,4 @@ export TROLLIUSDEBUG=1 python -m unittest discover -f python -m test.registry_tests -f -python -m test.queue_threads -f +#python -m test.queue_threads -f diff --git a/test/test_v2_endpoint_security.py b/test/test_v2_endpoint_security.py index bab7e1a5f..215c257b1 100644 --- a/test/test_v2_endpoint_security.py +++ b/test/test_v2_endpoint_security.py @@ -1,10 +1,9 @@ import unittest -import endpoints.decorated import json from app import app from initdb import setup_database_for_testing, finished_database_for_testing -from specs import build_v2_index_specs +from test.specs import build_v2_index_specs from endpoints.v2 import v2_bp app.register_blueprint(v2_bp, url_prefix='/v2')