From 48ba59d61552e3019406c5798da8dff31af51ca2 Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Wed, 22 Mar 2017 16:31:07 -0400 Subject: [PATCH] endpoints.v2: only work on docker repositories --- data/interfaces/v2.py | 25 +++++++++++-------------- data/model/repository.py | 3 ++- endpoints/v2/__init__.py | 16 ++++++++++++---- endpoints/v2/tag.py | 5 ----- endpoints/v2/v2auth.py | 25 ++++++++++++++++++++----- 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/data/interfaces/v2.py b/data/interfaces/v2.py index cb16334d6..afd77597e 100644 --- a/data/interfaces/v2.py +++ b/data/interfaces/v2.py @@ -13,9 +13,15 @@ _MEDIA_TYPE = "application/vnd.docker.distribution.manifest.v1+prettyjws" class Repository(namedtuple('Repository', ['id', 'name', 'namespace_name', 'description', - 'is_public'])): + 'is_public', 'kind'])): """ Repository represents a namespaced collection of tags. + :type id: int + :type name: string + :type namespace_name: string + :type description: string + :type is_public: bool + :type kind: string """ class ManifestJSON(namedtuple('ManifestJSON', ['digest', 'json', 'media_type'])): @@ -70,14 +76,6 @@ class DockerRegistryV2DataInterface(object): """ pass - @abstractmethod - def repository_is_public(self, namespace_name, repo_name): - """ - Returns true if the repository with the given name under the given namespace has public - visibility. - """ - pass - @abstractmethod def get_repository(self, namespace_name, repo_name): """ @@ -271,9 +269,6 @@ class PreOCIModel(DockerRegistryV2DataInterface): def create_repository(self, namespace_name, repo_name, creating_user=None): return model.repository.create_repository(namespace_name, repo_name, creating_user) - def repository_is_public(self, namespace_name, repo_name): - return model.repository.repository_is_public(namespace_name, repo_name) - def get_repository(self, namespace_name, repo_name): repo = model.repository.get_repository(namespace_name, repo_name) if repo is None: @@ -392,7 +387,8 @@ class PreOCIModel(DockerRegistryV2DataInterface): return [_tag_view(tag) for tag in tags_query] def get_visible_repositories(self, username, limit, offset): - query = model.repository.get_visible_repositories(username, include_public=(username is None)) + query = model.repository.get_visible_repositories(username, repo_kind='image', + include_public=(username is None)) query = query.limit(limit).offset(offset) return [_repository_for_repo(repo) for repo in query] @@ -538,7 +534,8 @@ def _repository_for_repo(repo): name=repo.name, namespace_name=repo.namespace_user.username, description=repo.description, - is_public=model.repository.is_repository_public(repo) + is_public=model.repository.is_repository_public(repo), + kind=model.repository.get_repo_kind_name(repo), ) diff --git a/data/model/repository.py b/data/model/repository.py index 6c8bd3da2..fbd47aeef 100644 --- a/data/model/repository.py +++ b/data/model/repository.py @@ -321,7 +321,8 @@ def get_visible_repositories(username, namespace=None, repo_kind='image', includ query = (Repository .select(Repository.name, Repository.id.alias('rid'), - Repository.description, Namespace.username, Repository.visibility) + Repository.description, Namespace.username, Repository.visibility, + Repository.kind) .switch(Repository) .join(Namespace, on=(Repository.namespace_user == Namespace.id))) diff --git a/endpoints/v2/__init__.py b/endpoints/v2/__init__.py index d6af69db0..483265818 100644 --- a/endpoints/v2/__init__.py +++ b/endpoints/v2/__init__.py @@ -15,9 +15,9 @@ from auth.auth_context import get_grant_context from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission, AdministerRepositoryPermission) from auth.registry_jwt_auth import process_registry_jwt_auth, get_auth_headers -from data import model +from data.interfaces.v2 import pre_oci_model as model from endpoints.decorators import anon_protect, anon_allowed -from endpoints.v2.errors import V2RegistryException, Unauthorized +from endpoints.v2.errors import V2RegistryException, Unauthorized, Unsupported, NameUnknown from util.http import abort from util.metrics.metricqueue import time_blueprint from util.registry.dockerver import docker_version @@ -96,13 +96,21 @@ def _require_repo_permission(permission_class, scopes=None, allow_public=False): def wrapped(namespace_name, repo_name, *args, **kwargs): logger.debug('Checking permission %s for repo: %s/%s', permission_class, namespace_name, repo_name) + repository = namespace_name + '/' + repo_name + repo = model.get_repository(namespace_name, repo_name) + if repo is None: + raise Unauthorized(repository=repository, scopes=scopes) + permission = permission_class(namespace_name, repo_name) if (permission.can() or (allow_public and - model.repository.repository_is_public(namespace_name, repo_name))): + repo.is_public)): + if repo.kind != 'image': + msg = 'This repository is for managing %s resources and not container images.' % repo.kind + raise Unsupported(detail=msg) return func(namespace_name, repo_name, *args, **kwargs) - repository = namespace_name + '/' + repo_name raise Unauthorized(repository=repository, scopes=scopes) + return wrapped return wrapper diff --git a/endpoints/v2/tag.py b/endpoints/v2/tag.py index 6b1ce20ad..683480ac2 100644 --- a/endpoints/v2/tag.py +++ b/endpoints/v2/tag.py @@ -3,7 +3,6 @@ from flask import jsonify 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, paginate -from endpoints.v2.errors import NameUnknown from endpoints.decorators import anon_protect from data.interfaces.v2 import pre_oci_model as model @@ -14,10 +13,6 @@ from data.interfaces.v2 import pre_oci_model as model @anon_protect @paginate() def list_all_tags(namespace_name, repo_name, limit, offset, pagination_callback): - repo = model.get_repository(namespace_name, repo_name) - if repo is None: - raise NameUnknown() - tags = model.repository_tags(namespace_name, repo_name, limit, offset) response = jsonify({ 'name': '{0}/{1}'.format(namespace_name, repo_name), diff --git a/endpoints/v2/v2auth.py b/endpoints/v2/v2auth.py index 3469044c4..9478ae785 100644 --- a/endpoints/v2/v2auth.py +++ b/endpoints/v2/v2auth.py @@ -91,15 +91,25 @@ def generate_registry_jwt(): final_actions = [] + repo = model.get_repository(namespace, reponame) + + repo_is_public = repo is not None and repo.is_public + invalid_repo_message = '' + if repo is not None and repo.kind != 'image': + invalid_repo_message = (('This repository is for managing %s resources ' + + 'and not container images.') % repo.kind) + if 'push' in actions: # If there is no valid user or token, then the repository cannot be # accessed. if user is not None or token is not None: # Lookup the repository. If it exists, make sure the entity has modify # permission. Otherwise, make sure the entity has create permission. - repo = model.get_repository(namespace, reponame) if repo: if ModifyRepositoryPermission(namespace, reponame).can(): + if repo.kind != 'image': + abort(405, invalid_repo_message) + final_actions.append('push') else: logger.debug('No permission to modify repository %s/%s', namespace, reponame) @@ -113,19 +123,24 @@ def generate_registry_jwt(): if 'pull' in actions: # Grant pull if the user can read the repo or it is public. - if (ReadRepositoryPermission(namespace, reponame).can() or - model.repository_is_public(namespace, reponame)): + if ReadRepositoryPermission(namespace, reponame).can() or repo_is_public: + if repo is not None and repo.kind != 'image': + abort(405, invalid_repo_message) + final_actions.append('pull') else: logger.debug('No permission to pull repository %s/%s', namespace, reponame) if '*' in actions: # Grant * user is admin - if (AdministerRepositoryPermission(namespace, reponame).can()): + if AdministerRepositoryPermission(namespace, reponame).can(): + if repo is not None and repo.kind != 'image': + abort(405, invalid_repo_message) + final_actions.append('*') else: logger.debug("No permission to administer repository %s/%s", namespace, reponame) - + # Add the access for the JWT. access.append({ 'type': 'repository',