commit
3ccf3c5f33
40 changed files with 790 additions and 136 deletions
|
@ -22,7 +22,7 @@ from auth.auth_context import get_authenticated_user, get_validated_oauth_token
|
|||
from auth.process import process_oauth
|
||||
from endpoints.csrf import csrf_protect
|
||||
from endpoints.exception import (ApiException, Unauthorized, InvalidRequest, InvalidResponse,
|
||||
FreshLoginRequired)
|
||||
FreshLoginRequired, NotFound)
|
||||
from endpoints.decorators import check_anon_protection
|
||||
from util.metrics.metricqueue import time_decorator
|
||||
from util.names import parse_namespace_repository
|
||||
|
@ -200,6 +200,20 @@ class RepositoryParamResource(ApiResource):
|
|||
method_decorators = [check_anon_protection, parse_repository_name]
|
||||
|
||||
|
||||
def disallow_for_app_repositories(func):
|
||||
@wraps(func)
|
||||
def wrapped(self, namespace, repository, *args, **kwargs):
|
||||
# Lookup the repository with the given namespace and name and ensure it is not an application
|
||||
# repository.
|
||||
repo = model.repository.get_repository(namespace, repository, kind_filter='application')
|
||||
if repo:
|
||||
abort(501)
|
||||
|
||||
return func(self, namespace, repository, *args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def require_repo_permission(permission_class, scope, allow_public=False):
|
||||
def wrapper(func):
|
||||
@add_method_metadata('oauth2_scope', scope)
|
||||
|
|
|
@ -14,7 +14,7 @@ from buildtrigger.basehandler import BuildTriggerHandler
|
|||
from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource,
|
||||
require_repo_read, require_repo_write, validate_json_request,
|
||||
ApiResource, internal_only, format_date, api, path_param,
|
||||
require_repo_admin, abort)
|
||||
require_repo_admin, abort, disallow_for_app_repositories)
|
||||
from endpoints.exception import Unauthorized, NotFound, InvalidRequest
|
||||
from endpoints.building import start_build, PreparedBuild, MaximumBuildsQueuedException
|
||||
from data import database
|
||||
|
@ -200,6 +200,7 @@ class RepositoryBuildList(RepositoryParamResource):
|
|||
@query_param('limit', 'The maximum number of builds to return', type=int, default=5)
|
||||
@query_param('since', 'Returns all builds since the given unix timecode', type=int, default=None)
|
||||
@nickname('getRepoBuilds')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository, parsed_args):
|
||||
""" Get the list of repository builds. """
|
||||
limit = parsed_args.get('limit', 5)
|
||||
|
@ -215,6 +216,7 @@ class RepositoryBuildList(RepositoryParamResource):
|
|||
|
||||
@require_repo_write
|
||||
@nickname('requestRepoBuild')
|
||||
@disallow_for_app_repositories
|
||||
@validate_json_request('RepositoryBuildRequest')
|
||||
def post(self, namespace, repository):
|
||||
""" Request that a repository be built and pushed from the specified input. """
|
||||
|
@ -315,6 +317,7 @@ class RepositoryBuildResource(RepositoryParamResource):
|
|||
""" Resource for dealing with repository builds. """
|
||||
@require_repo_read
|
||||
@nickname('getRepoBuild')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository, build_uuid):
|
||||
""" Returns information about a build. """
|
||||
try:
|
||||
|
@ -329,6 +332,7 @@ class RepositoryBuildResource(RepositoryParamResource):
|
|||
|
||||
@require_repo_admin
|
||||
@nickname('cancelRepoBuild')
|
||||
@disallow_for_app_repositories
|
||||
def delete(self, namespace, repository, build_uuid):
|
||||
""" Cancels a repository build. """
|
||||
try:
|
||||
|
@ -352,6 +356,7 @@ class RepositoryBuildStatus(RepositoryParamResource):
|
|||
""" Resource for dealing with repository build status. """
|
||||
@require_repo_read
|
||||
@nickname('getRepoBuildStatus')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository, build_uuid):
|
||||
""" Return the status for the builds specified by the build uuids. """
|
||||
build = model.build.get_repository_build(build_uuid)
|
||||
|
@ -392,6 +397,7 @@ class RepositoryBuildLogs(RepositoryParamResource):
|
|||
""" Resource for loading repository build logs. """
|
||||
@require_repo_write
|
||||
@nickname('getRepoBuildLogs')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository, build_uuid):
|
||||
""" Return the build logs for the build specified by the build uuid. """
|
||||
build = model.build.get_repository_build(build_uuid)
|
||||
|
|
|
@ -4,7 +4,7 @@ import json
|
|||
|
||||
from collections import defaultdict
|
||||
from endpoints.api import (resource, nickname, require_repo_read, RepositoryParamResource,
|
||||
format_date, path_param)
|
||||
format_date, path_param, disallow_for_app_repositories)
|
||||
from endpoints.exception import NotFound
|
||||
from data import model
|
||||
|
||||
|
@ -49,6 +49,7 @@ class RepositoryImageList(RepositoryParamResource):
|
|||
""" Resource for listing repository images. """
|
||||
@require_repo_read
|
||||
@nickname('listRepositoryImages')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository):
|
||||
""" List the images for the specified repository. """
|
||||
repo = model.repository.get_repository(namespace, repository)
|
||||
|
@ -89,6 +90,7 @@ class RepositoryImage(RepositoryParamResource):
|
|||
""" Resource for handling repository images. """
|
||||
@require_repo_read
|
||||
@nickname('getImage')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository, image_id):
|
||||
""" Get the information available for the specified image. """
|
||||
image = model.image.get_repo_image_extended(namespace, repository, image_id)
|
||||
|
|
|
@ -4,7 +4,8 @@ from app import label_validator
|
|||
from flask import request
|
||||
from endpoints.api import (resource, nickname, require_repo_read, require_repo_write,
|
||||
RepositoryParamResource, log_action, validate_json_request,
|
||||
path_param, parse_args, query_param, truthy_bool, abort, api)
|
||||
path_param, parse_args, query_param, truthy_bool, abort, api,
|
||||
disallow_for_app_repositories)
|
||||
from endpoints.exception import NotFound
|
||||
from data import model
|
||||
|
||||
|
@ -59,6 +60,7 @@ class RepositoryManifestLabels(RepositoryParamResource):
|
|||
|
||||
@require_repo_read
|
||||
@nickname('listManifestLabels')
|
||||
@disallow_for_app_repositories
|
||||
@parse_args()
|
||||
@query_param('filter', 'If specified, only labels matching the given prefix will be returned',
|
||||
type=str, default=None)
|
||||
|
@ -75,6 +77,7 @@ class RepositoryManifestLabels(RepositoryParamResource):
|
|||
|
||||
@require_repo_write
|
||||
@nickname('addManifestLabel')
|
||||
@disallow_for_app_repositories
|
||||
@validate_json_request('AddLabel')
|
||||
def post(self, namespace, repository, manifestref):
|
||||
""" Adds a new label into the tag manifest. """
|
||||
|
@ -121,6 +124,7 @@ class ManageRepositoryManifestLabel(RepositoryParamResource):
|
|||
""" Resource for managing the labels on a specific repository manifest. """
|
||||
@require_repo_read
|
||||
@nickname('getManifestLabel')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository, manifestref, labelid):
|
||||
""" Retrieves the label with the specific ID under the manifest. """
|
||||
try:
|
||||
|
@ -137,6 +141,7 @@ class ManageRepositoryManifestLabel(RepositoryParamResource):
|
|||
|
||||
@require_repo_write
|
||||
@nickname('deleteManifestLabel')
|
||||
@disallow_for_app_repositories
|
||||
def delete(self, namespace, repository, manifestref, labelid):
|
||||
""" Deletes an existing label from a manifest. """
|
||||
try:
|
||||
|
|
|
@ -14,7 +14,7 @@ from endpoints.api import (truthy_bool, format_date, nickname, log_action, valid
|
|||
require_repo_read, require_repo_write, require_repo_admin,
|
||||
RepositoryParamResource, resource, query_param, parse_args, ApiResource,
|
||||
request_error, require_scope, path_param, page_support, parse_args,
|
||||
query_param, truthy_bool)
|
||||
query_param, truthy_bool, disallow_for_app_repositories)
|
||||
from endpoints.exception import Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException
|
||||
from endpoints.api.billing import lookup_allowed_private_repos, get_namespace_plan
|
||||
from endpoints.api.subscribe import check_repository_usage
|
||||
|
@ -77,6 +77,11 @@ class RepositoryList(ApiResource):
|
|||
'type': 'string',
|
||||
'description': 'Markdown encoded description for the repository',
|
||||
},
|
||||
'kind': {
|
||||
'type': 'string',
|
||||
'description': 'The kind of repository',
|
||||
'enum': ['image', 'application'],
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -111,7 +116,9 @@ class RepositoryList(ApiResource):
|
|||
if not REPOSITORY_NAME_REGEX.match(repository_name):
|
||||
raise InvalidRequest('Invalid repository name')
|
||||
|
||||
repo = model.repository.create_repository(namespace_name, repository_name, owner, visibility)
|
||||
kind = req.get('kind', 'image')
|
||||
repo = model.repository.create_repository(namespace_name, repository_name, owner, visibility,
|
||||
repo_kind=kind)
|
||||
repo.description = req['description']
|
||||
repo.save()
|
||||
|
||||
|
@ -354,6 +361,7 @@ class Repository(RepositoryParamResource):
|
|||
|
||||
@require_repo_admin
|
||||
@nickname('deleteRepository')
|
||||
@disallow_for_app_repositories
|
||||
def delete(self, namespace, repository):
|
||||
""" Delete a repository. """
|
||||
model.repository.purge_repository(namespace, repository)
|
||||
|
|
|
@ -7,7 +7,7 @@ from flask import request
|
|||
from app import notification_queue
|
||||
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
|
||||
log_action, validate_json_request, request_error,
|
||||
path_param)
|
||||
path_param, disallow_for_app_repositories)
|
||||
from endpoints.exception import NotFound
|
||||
from endpoints.notificationevent import NotificationEvent
|
||||
from endpoints.notificationmethod import (NotificationMethod,
|
||||
|
@ -80,6 +80,7 @@ class RepositoryNotificationList(RepositoryParamResource):
|
|||
|
||||
@require_repo_admin
|
||||
@nickname('createRepoNotification')
|
||||
@disallow_for_app_repositories
|
||||
@validate_json_request('NotificationCreateRequest')
|
||||
def post(self, namespace, repository):
|
||||
""" Create a new notification for the specified repository. """
|
||||
|
@ -110,6 +111,7 @@ class RepositoryNotificationList(RepositoryParamResource):
|
|||
|
||||
@require_repo_admin
|
||||
@nickname('listRepoNotifications')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository):
|
||||
""" List the notifications for the specified repository. """
|
||||
notifications = model.notification.list_repo_notifications(namespace, repository)
|
||||
|
@ -125,6 +127,7 @@ class RepositoryNotification(RepositoryParamResource):
|
|||
""" Resource for dealing with specific notifications. """
|
||||
@require_repo_admin
|
||||
@nickname('getRepoNotification')
|
||||
@disallow_for_app_repositories
|
||||
def get(self, namespace, repository, uuid):
|
||||
""" Get information for the specified notification. """
|
||||
try:
|
||||
|
@ -140,6 +143,7 @@ class RepositoryNotification(RepositoryParamResource):
|
|||
|
||||
@require_repo_admin
|
||||
@nickname('deleteRepoNotification')
|
||||
@disallow_for_app_repositories
|
||||
def delete(self, namespace, repository, uuid):
|
||||
""" Deletes the specified notification. """
|
||||
deleted = model.notification.delete_repo_notification(namespace, repository, uuid)
|
||||
|
@ -158,6 +162,7 @@ class TestRepositoryNotification(RepositoryParamResource):
|
|||
""" Resource for queuing a test of a notification. """
|
||||
@require_repo_admin
|
||||
@nickname('testRepoNotification')
|
||||
@disallow_for_app_repositories
|
||||
def post(self, namespace, repository, uuid):
|
||||
""" Queues a test notification for this repository. """
|
||||
try:
|
||||
|
|
|
@ -7,7 +7,7 @@ from app import secscan_api
|
|||
from data import model
|
||||
from endpoints.api import (require_repo_read, path_param,
|
||||
RepositoryParamResource, resource, nickname, show_if, parse_args,
|
||||
query_param, truthy_bool)
|
||||
query_param, truthy_bool, disallow_for_app_repositories)
|
||||
from endpoints.exception import NotFound, DownstreamIssue
|
||||
from endpoints.api.manifest import MANIFEST_DIGEST_ROUTE
|
||||
from util.secscan.api import APIRequestFailure
|
||||
|
@ -67,6 +67,7 @@ class RepositoryImageSecurity(RepositoryParamResource):
|
|||
|
||||
@require_repo_read
|
||||
@nickname('getRepoImageSecurity')
|
||||
@disallow_for_app_repositories
|
||||
@parse_args()
|
||||
@query_param('vulnerabilities', 'Include vulnerabilities informations', type=truthy_bool,
|
||||
default=False)
|
||||
|
@ -88,6 +89,7 @@ class RepositoryManifestSecurity(RepositoryParamResource):
|
|||
|
||||
@require_repo_read
|
||||
@nickname('getRepoManifestSecurity')
|
||||
@disallow_for_app_repositories
|
||||
@parse_args()
|
||||
@query_param('vulnerabilities', 'Include vulnerabilities informations', type=truthy_bool,
|
||||
default=False)
|
||||
|
|
|
@ -4,7 +4,8 @@ from flask import request, abort
|
|||
|
||||
from endpoints.api import (resource, nickname, require_repo_read, require_repo_write,
|
||||
RepositoryParamResource, log_action, validate_json_request,
|
||||
path_param, parse_args, query_param, truthy_bool)
|
||||
path_param, parse_args, query_param, truthy_bool,
|
||||
disallow_for_app_repositories)
|
||||
from endpoints.exception import NotFound
|
||||
from endpoints.api.image import image_view
|
||||
from data import model
|
||||
|
@ -18,6 +19,7 @@ class ListRepositoryTags(RepositoryParamResource):
|
|||
""" Resource for listing full repository tag history, alive *and dead*. """
|
||||
|
||||
@require_repo_read
|
||||
@disallow_for_app_repositories
|
||||
@parse_args()
|
||||
@query_param('specificTag', 'Filters the tags to the specific tag.', type=str, default='')
|
||||
@query_param('limit', 'Limit to the number of results to return per page. Max 100.', type=int, default=50)
|
||||
|
@ -82,6 +84,7 @@ class RepositoryTag(RepositoryParamResource):
|
|||
}
|
||||
|
||||
@require_repo_write
|
||||
@disallow_for_app_repositories
|
||||
@nickname('changeTagImage')
|
||||
@validate_json_request('MoveTag')
|
||||
def put(self, namespace, repository, tag):
|
||||
|
@ -116,6 +119,7 @@ class RepositoryTag(RepositoryParamResource):
|
|||
return 'Updated', 201
|
||||
|
||||
@require_repo_write
|
||||
@disallow_for_app_repositories
|
||||
@nickname('deleteFullTag')
|
||||
def delete(self, namespace, repository, tag):
|
||||
""" Delete the specified repository tag. """
|
||||
|
@ -136,6 +140,7 @@ class RepositoryTagImages(RepositoryParamResource):
|
|||
""" Resource for listing the images in a specific repository tag. """
|
||||
@require_repo_read
|
||||
@nickname('listTagImages')
|
||||
@disallow_for_app_repositories
|
||||
@parse_args()
|
||||
@query_param('owned', 'If specified, only images wholely owned by this tag are returned.',
|
||||
type=truthy_bool, default=False)
|
||||
|
@ -206,6 +211,7 @@ class RestoreTag(RepositoryParamResource):
|
|||
}
|
||||
|
||||
@require_repo_write
|
||||
@disallow_for_app_repositories
|
||||
@nickname('restoreTag')
|
||||
@validate_json_request('RestoreTag')
|
||||
def post(self, namespace, repository, tag):
|
||||
|
|
0
endpoints/api/test/__init__.py
Normal file
0
endpoints/api/test/__init__.py
Normal file
58
endpoints/api/test/shared.py
Normal file
58
endpoints/api/test/shared.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
import datetime
|
||||
import json
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from data import model
|
||||
from endpoints.api import api
|
||||
|
||||
CSRF_TOKEN_KEY = '_csrf_token'
|
||||
CSRF_TOKEN = '123csrfforme'
|
||||
|
||||
|
||||
@contextmanager
|
||||
def client_with_identity(auth_username, client):
|
||||
with client.session_transaction() as sess:
|
||||
if auth_username:
|
||||
if auth_username is not None:
|
||||
loaded = model.user.get_user(auth_username)
|
||||
sess['user_id'] = loaded.uuid
|
||||
sess['login_time'] = datetime.datetime.now()
|
||||
sess[CSRF_TOKEN_KEY] = CSRF_TOKEN
|
||||
|
||||
yield client
|
||||
|
||||
with client.session_transaction() as sess:
|
||||
sess['user_id'] = None
|
||||
sess['login_time'] = None
|
||||
sess[CSRF_TOKEN_KEY] = None
|
||||
|
||||
|
||||
def add_csrf_param(params):
|
||||
""" Returns a params dict with the CSRF parameter added. """
|
||||
params = params or {}
|
||||
params[CSRF_TOKEN_KEY] = CSRF_TOKEN
|
||||
return params
|
||||
|
||||
|
||||
def conduct_api_call(client, resource, method, params, body=None, expected_code=200):
|
||||
""" Conducts an API call to the given resource via the given client, and ensures its returned
|
||||
status matches the code given.
|
||||
|
||||
Returns the response.
|
||||
"""
|
||||
params = add_csrf_param(params)
|
||||
|
||||
final_url = api.url_for(resource, **params)
|
||||
|
||||
headers = {}
|
||||
headers.update({"Content-Type": "application/json"})
|
||||
|
||||
if body is not None:
|
||||
body = json.dumps(body)
|
||||
|
||||
rv = client.open(final_url, method=method, data=body, headers=headers)
|
||||
msg = '%s %s: got %s expected: %s | %s' % (method, final_url, rv.status_code, expected_code,
|
||||
rv.data)
|
||||
assert rv.status_code == expected_code, msg
|
||||
return rv
|
80
endpoints/api/test/test_disallow_for_apps.py
Normal file
80
endpoints/api/test/test_disallow_for_apps.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
import pytest
|
||||
|
||||
from data import model
|
||||
from endpoints.api.repository import Repository
|
||||
from endpoints.api.build import (RepositoryBuildList, RepositoryBuildResource,
|
||||
RepositoryBuildStatus, RepositoryBuildLogs)
|
||||
from endpoints.api.image import RepositoryImageList, RepositoryImage
|
||||
from endpoints.api.manifest import RepositoryManifestLabels, ManageRepositoryManifestLabel
|
||||
from endpoints.api.repositorynotification import (RepositoryNotification,
|
||||
RepositoryNotificationList,
|
||||
TestRepositoryNotification)
|
||||
from endpoints.api.secscan import RepositoryImageSecurity, RepositoryManifestSecurity
|
||||
from endpoints.api.tag import ListRepositoryTags, RepositoryTag, RepositoryTagImages, RestoreTag
|
||||
from endpoints.api.trigger import (BuildTriggerList, BuildTrigger, BuildTriggerSubdirs,
|
||||
BuildTriggerActivate, BuildTriggerAnalyze, ActivateBuildTrigger,
|
||||
TriggerBuildList, BuildTriggerFieldValues, BuildTriggerSources,
|
||||
BuildTriggerSourceNamespaces)
|
||||
from endpoints.api.test.shared import client_with_identity, conduct_api_call
|
||||
from endpoints.test.fixtures import app, appconfig, database_uri, init_db_path, sqlitedb_file
|
||||
|
||||
BUILD_ARGS = {'build_uuid': '1234'}
|
||||
IMAGE_ARGS = {'imageid': '1234', 'image_id': 1234}
|
||||
MANIFEST_ARGS = {'manifestref': 'sha256:abcd1234'}
|
||||
LABEL_ARGS = {'manifestref': 'sha256:abcd1234', 'labelid': '1234'}
|
||||
NOTIFICATION_ARGS = {'uuid': '1234'}
|
||||
TAG_ARGS = {'tag': 'foobar'}
|
||||
TRIGGER_ARGS = {'trigger_uuid': '1234'}
|
||||
FIELD_ARGS = {'trigger_uuid': '1234', 'field_name': 'foobar'}
|
||||
|
||||
@pytest.mark.parametrize('resource, method, params', [
|
||||
(Repository, 'delete', None),
|
||||
(RepositoryBuildList, 'get', None),
|
||||
(RepositoryBuildList, 'post', None),
|
||||
(RepositoryBuildResource, 'get', BUILD_ARGS),
|
||||
(RepositoryBuildResource, 'delete', BUILD_ARGS),
|
||||
(RepositoryBuildStatus, 'get', BUILD_ARGS),
|
||||
(RepositoryBuildLogs, 'get', BUILD_ARGS),
|
||||
(RepositoryImageList, 'get', None),
|
||||
(RepositoryImage, 'get', IMAGE_ARGS),
|
||||
(RepositoryManifestLabels, 'get', MANIFEST_ARGS),
|
||||
(RepositoryManifestLabels, 'post', MANIFEST_ARGS),
|
||||
(ManageRepositoryManifestLabel, 'get', LABEL_ARGS),
|
||||
(ManageRepositoryManifestLabel, 'delete', LABEL_ARGS),
|
||||
(RepositoryNotificationList, 'get', None),
|
||||
(RepositoryNotificationList, 'post', None),
|
||||
(RepositoryNotification, 'get', NOTIFICATION_ARGS),
|
||||
(RepositoryNotification, 'delete', NOTIFICATION_ARGS),
|
||||
(TestRepositoryNotification, 'post', NOTIFICATION_ARGS),
|
||||
(RepositoryImageSecurity, 'get', IMAGE_ARGS),
|
||||
(RepositoryManifestSecurity, 'get', MANIFEST_ARGS),
|
||||
(ListRepositoryTags, 'get', None),
|
||||
(RepositoryTag, 'put', TAG_ARGS),
|
||||
(RepositoryTag, 'delete', TAG_ARGS),
|
||||
(RepositoryTagImages, 'get', TAG_ARGS),
|
||||
(RestoreTag, 'post', TAG_ARGS),
|
||||
(BuildTriggerList, 'get', None),
|
||||
(BuildTrigger, 'get', TRIGGER_ARGS),
|
||||
(BuildTrigger, 'delete', TRIGGER_ARGS),
|
||||
(BuildTriggerSubdirs, 'post', TRIGGER_ARGS),
|
||||
(BuildTriggerActivate, 'post', TRIGGER_ARGS),
|
||||
(BuildTriggerAnalyze, 'post', TRIGGER_ARGS),
|
||||
(ActivateBuildTrigger, 'post', TRIGGER_ARGS),
|
||||
(TriggerBuildList, 'get', TRIGGER_ARGS),
|
||||
(BuildTriggerFieldValues, 'post', FIELD_ARGS),
|
||||
(BuildTriggerSources, 'post', TRIGGER_ARGS),
|
||||
(BuildTriggerSourceNamespaces, 'get', TRIGGER_ARGS),
|
||||
])
|
||||
def test_disallowed_for_apps(resource, method, params, client):
|
||||
namespace = 'devtable'
|
||||
repository = 'someapprepo'
|
||||
|
||||
devtable = model.user.get_user('devtable')
|
||||
model.repository.create_repository(namespace, repository, devtable, repo_kind='application')
|
||||
|
||||
params = params or {}
|
||||
params['repository'] = '%s/%s' % (namespace, repository)
|
||||
|
||||
with client_with_identity('devtable', client) as cl:
|
||||
conduct_api_call(cl, resource, method, params, None, 501)
|
||||
|
|
@ -1,44 +1,29 @@
|
|||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from data import model
|
||||
from endpoints.api import api
|
||||
from endpoints.api.test.shared import client_with_identity, conduct_api_call
|
||||
from endpoints.api.superuser import SuperUserRepositoryBuildLogs, SuperUserRepositoryBuildResource
|
||||
from endpoints.api.superuser import SuperUserRepositoryBuildStatus
|
||||
from endpoints.test.fixtures import app, appconfig, database_uri, init_db_path, sqlitedb_file
|
||||
|
||||
TEAM_PARAMS = {'orgname': 'buynlarge', 'teamname': 'owners'}
|
||||
BUILD_PARAMS = {'build_uuid': 'test-1234'}
|
||||
|
||||
def client_with_identity(auth_username, client):
|
||||
with client.session_transaction() as sess:
|
||||
if auth_username:
|
||||
if auth_username is not None:
|
||||
loaded = model.user.get_user(auth_username)
|
||||
sess['user_id'] = loaded.uuid
|
||||
sess['login_time'] = datetime.datetime.now()
|
||||
return client
|
||||
@pytest.mark.parametrize('resource,method,params,body,identity,expected', [
|
||||
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, None, 401),
|
||||
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, 'freshuser', 403),
|
||||
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, 'reader', 403),
|
||||
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, 'devtable', 400),
|
||||
|
||||
(SuperUserRepositoryBuildStatus, 'GET', BUILD_PARAMS, None, None, 401),
|
||||
(SuperUserRepositoryBuildStatus, 'GET', BUILD_PARAMS, None, 'freshuser', 403),
|
||||
(SuperUserRepositoryBuildStatus, 'GET', BUILD_PARAMS, None, 'reader', 403),
|
||||
(SuperUserRepositoryBuildStatus, 'GET', BUILD_PARAMS, None, 'devtable', 400),
|
||||
|
||||
@pytest.mark.parametrize('resource,identity,expected', [
|
||||
(SuperUserRepositoryBuildLogs, None, 401),
|
||||
(SuperUserRepositoryBuildLogs, 'freshuser', 403),
|
||||
(SuperUserRepositoryBuildLogs, 'reader', 403),
|
||||
(SuperUserRepositoryBuildLogs, 'devtable', 400),
|
||||
|
||||
(SuperUserRepositoryBuildStatus, None, 401),
|
||||
(SuperUserRepositoryBuildStatus, 'freshuser', 403),
|
||||
(SuperUserRepositoryBuildStatus, 'reader', 403),
|
||||
(SuperUserRepositoryBuildStatus, 'devtable', 400),
|
||||
|
||||
(SuperUserRepositoryBuildResource, None, 401),
|
||||
(SuperUserRepositoryBuildResource, 'freshuser', 403),
|
||||
(SuperUserRepositoryBuildResource, 'reader', 403),
|
||||
(SuperUserRepositoryBuildResource, 'devtable', 404),
|
||||
(SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, None, 401),
|
||||
(SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'freshuser', 403),
|
||||
(SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'reader', 403),
|
||||
(SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'devtable', 404),
|
||||
])
|
||||
def test_super_user_build_endpoints(resource, identity, expected, client):
|
||||
cl = client_with_identity(identity, client)
|
||||
final_url = api.url_for(resource, build_uuid='1234')
|
||||
rv = cl.open(final_url)
|
||||
msg = '%s %s: %s expected: %s' % ('GET', final_url, rv.status_code, expected)
|
||||
assert rv.status_code == expected, msg
|
||||
|
||||
def test_api_security(resource, method, params, body, identity, expected, client):
|
||||
with client_with_identity(identity, client) as cl:
|
||||
conduct_api_call(cl, resource, method, params, body, expected)
|
||||
|
|
|
@ -15,7 +15,8 @@ from buildtrigger.triggerutil import (TriggerDeactivationException,
|
|||
RepositoryReadException, TriggerStartException)
|
||||
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
|
||||
log_action, request_error, query_param, parse_args, internal_only,
|
||||
validate_json_request, api, path_param, abort)
|
||||
validate_json_request, api, path_param, abort,
|
||||
disallow_for_app_repositories)
|
||||
from endpoints.exception import NotFound, Unauthorized, InvalidRequest
|
||||
from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus
|
||||
from endpoints.building import start_build, MaximumBuildsQueuedException
|
||||
|
@ -40,6 +41,7 @@ class BuildTriggerList(RepositoryParamResource):
|
|||
""" Resource for listing repository build triggers. """
|
||||
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('listBuildTriggers')
|
||||
def get(self, namespace_name, repo_name):
|
||||
""" List the triggers for the specified repository. """
|
||||
|
@ -56,6 +58,7 @@ class BuildTrigger(RepositoryParamResource):
|
|||
""" Resource for managing specific build triggers. """
|
||||
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('getBuildTrigger')
|
||||
def get(self, namespace_name, repo_name, trigger_uuid):
|
||||
""" Get information for the specified build trigger. """
|
||||
|
@ -67,6 +70,7 @@ class BuildTrigger(RepositoryParamResource):
|
|||
return trigger_view(trigger, can_admin=True)
|
||||
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('deleteBuildTrigger')
|
||||
def delete(self, namespace_name, repo_name, trigger_uuid):
|
||||
""" Delete the specified build trigger. """
|
||||
|
@ -110,6 +114,7 @@ class BuildTriggerSubdirs(RepositoryParamResource):
|
|||
}
|
||||
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('listBuildTriggerSubdirs')
|
||||
@validate_json_request('BuildTriggerSubdirRequest')
|
||||
def post(self, namespace_name, repo_name, trigger_uuid):
|
||||
|
@ -170,6 +175,7 @@ class BuildTriggerActivate(RepositoryParamResource):
|
|||
}
|
||||
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('activateBuildTrigger')
|
||||
@validate_json_request('BuildTriggerActivateRequest')
|
||||
def post(self, namespace_name, repo_name, trigger_uuid):
|
||||
|
@ -271,6 +277,7 @@ class BuildTriggerAnalyze(RepositoryParamResource):
|
|||
}
|
||||
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('analyzeBuildTrigger')
|
||||
@validate_json_request('BuildTriggerAnalyzeRequest')
|
||||
def post(self, namespace_name, repo_name, trigger_uuid):
|
||||
|
@ -420,6 +427,7 @@ class ActivateBuildTrigger(RepositoryParamResource):
|
|||
}
|
||||
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('manuallyStartBuildTrigger')
|
||||
@validate_json_request('RunParameters')
|
||||
def post(self, namespace_name, repo_name, trigger_uuid):
|
||||
|
@ -460,6 +468,7 @@ class ActivateBuildTrigger(RepositoryParamResource):
|
|||
class TriggerBuildList(RepositoryParamResource):
|
||||
""" Resource to represent builds that were activated from the specified trigger. """
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@parse_args()
|
||||
@query_param('limit', 'The maximum number of builds to return', type=int, default=5)
|
||||
@nickname('listTriggerRecentBuilds')
|
||||
|
@ -479,6 +488,7 @@ FIELD_VALUE_LIMIT = 30
|
|||
class BuildTriggerFieldValues(RepositoryParamResource):
|
||||
""" Custom verb to fetch a values list for a particular field name. """
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('listTriggerFieldValues')
|
||||
def post(self, namespace_name, repo_name, trigger_uuid, field_name):
|
||||
""" List the field values for a custom run field. """
|
||||
|
@ -522,6 +532,7 @@ class BuildTriggerSources(RepositoryParamResource):
|
|||
}
|
||||
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('listTriggerBuildSources')
|
||||
@validate_json_request('BuildTriggerSourcesRequest')
|
||||
def post(self, namespace_name, repo_name, trigger_uuid):
|
||||
|
@ -555,6 +566,7 @@ class BuildTriggerSources(RepositoryParamResource):
|
|||
class BuildTriggerSourceNamespaces(RepositoryParamResource):
|
||||
""" Custom verb to fetch the list of namespaces (orgs, projects, etc) for the trigger config. """
|
||||
@require_repo_admin
|
||||
@disallow_for_app_repositories
|
||||
@nickname('listTriggerBuildSourceNamespaces')
|
||||
def get(self, namespace_name, repo_name, trigger_uuid):
|
||||
""" List the build sources for the trigger configuration thus far. """
|
||||
|
|
|
@ -29,6 +29,9 @@ class MaximumBuildsQueuedException(Exception):
|
|||
|
||||
|
||||
def start_build(repository, prepared_build, pull_robot_name=None):
|
||||
if repository.kind.name != 'image':
|
||||
raise Exception('Attempt to start a build for application repository %s' % repository.id)
|
||||
|
||||
if MAX_BUILD_QUEUE_RATE_ITEMS > 0 and MAX_BUILD_QUEUE_RATE_SECS > 0:
|
||||
queue_item_canonical_name = [repository.namespace_user.username, repository.name]
|
||||
now = datetime.utcnow()
|
||||
|
|
|
@ -31,6 +31,8 @@ def attach_github_build_trigger(namespace_name, repo_name):
|
|||
if not repo:
|
||||
msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
|
||||
abort(404, message=msg)
|
||||
elif repo.kind.name != 'image':
|
||||
abort(501)
|
||||
|
||||
trigger = model.build.create_build_trigger(repo, 'github', token, current_user.db_user())
|
||||
repo_path = '%s/%s' % (namespace_name, repo_name)
|
||||
|
|
|
@ -44,6 +44,8 @@ def attach_gitlab_build_trigger():
|
|||
if not repo:
|
||||
msg = 'Invalid repository: %s/%s' % (namespace, repository)
|
||||
abort(404, message=msg)
|
||||
elif repo.kind.name != 'image':
|
||||
abort(501)
|
||||
|
||||
trigger = model.build.create_build_trigger(repo, 'gitlab', token, current_user.db_user())
|
||||
repo_path = '%s/%s' % (namespace, repository)
|
||||
|
|
|
@ -182,6 +182,10 @@ def create_repository(namespace_name, repo_name):
|
|||
message='You do not have permission to modify repository %(namespace)s/%(repository)s',
|
||||
issue='no-repo-write-permission',
|
||||
namespace=namespace_name, repository=repo_name)
|
||||
elif repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, namespace=namespace_name)
|
||||
|
||||
else:
|
||||
create_perm = CreateRepositoryPermission(namespace_name)
|
||||
if not create_perm.can():
|
||||
|
@ -223,6 +227,9 @@ def update_images(namespace_name, repo_name):
|
|||
if not repo:
|
||||
# Make sure the repo actually exists.
|
||||
abort(404, message='Unknown repository', issue='unknown-repo')
|
||||
elif repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, namespace=namespace_name)
|
||||
|
||||
# Generate a job for each notification that has been added to this repo
|
||||
logger.debug('Adding notifications for repository')
|
||||
|
@ -255,6 +262,9 @@ def get_repository_images(namespace_name, repo_name):
|
|||
repo = model.get_repository(namespace_name, repo_name)
|
||||
if not repo:
|
||||
abort(404, message='Unknown repository', issue='unknown-repo')
|
||||
elif repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, namespace=namespace_name)
|
||||
|
||||
logger.debug('Building repository image response')
|
||||
resp = make_response(json.dumps([]), 200)
|
||||
|
|
|
@ -83,6 +83,11 @@ def head_image_layer(namespace, repository, image_id, headers):
|
|||
|
||||
logger.debug('Checking repo permissions')
|
||||
if permission.can() or model.repository_is_public(namespace, repository):
|
||||
repo = model.get_repository(namespace, repository)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, image_id=image_id)
|
||||
|
||||
logger.debug('Looking up placement locations')
|
||||
locations, _ = model.placement_locations_and_path_docker_v1(namespace, repository, image_id)
|
||||
if locations is None:
|
||||
|
@ -116,6 +121,11 @@ def get_image_layer(namespace, repository, image_id, headers):
|
|||
|
||||
logger.debug('Checking repo permissions')
|
||||
if permission.can() or model.repository_is_public(namespace, repository):
|
||||
repo = model.get_repository(namespace, repository)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, image_id=image_id)
|
||||
|
||||
logger.debug('Looking up placement locations and path')
|
||||
locations, path = model.placement_locations_and_path_docker_v1(namespace, repository, image_id)
|
||||
if not locations or not path:
|
||||
|
@ -151,6 +161,11 @@ def put_image_layer(namespace, repository, image_id):
|
|||
if not permission.can():
|
||||
abort(403)
|
||||
|
||||
repo = model.get_repository(namespace, repository)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, image_id=image_id)
|
||||
|
||||
logger.debug('Retrieving image')
|
||||
if model.storage_exists(namespace, repository, image_id):
|
||||
exact_abort(409, 'Image already exists')
|
||||
|
@ -255,6 +270,11 @@ def put_image_checksum(namespace, repository, image_id):
|
|||
if not permission.can():
|
||||
abort(403)
|
||||
|
||||
repo = model.get_repository(namespace, repository)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, image_id=image_id)
|
||||
|
||||
# Docker Version < 0.10 (tarsum+sha):
|
||||
old_checksum = request.headers.get('X-Docker-Checksum')
|
||||
|
||||
|
@ -324,6 +344,11 @@ def get_image_json(namespace, repository, image_id, headers):
|
|||
if not permission.can() and not model.repository_is_public(namespace, repository):
|
||||
abort(403)
|
||||
|
||||
repo = model.get_repository(namespace, repository)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, image_id=image_id)
|
||||
|
||||
logger.debug('Looking up repo image')
|
||||
v1_metadata = model.docker_v1_metadata(namespace, repository, image_id)
|
||||
if v1_metadata is None:
|
||||
|
@ -353,6 +378,11 @@ def get_image_ancestry(namespace, repository, image_id, headers):
|
|||
if not permission.can() and not model.repository_is_public(namespace, repository):
|
||||
abort(403)
|
||||
|
||||
repo = model.get_repository(namespace, repository)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, image_id=image_id)
|
||||
|
||||
ancestry_docker_ids = model.image_ancestry(namespace, repository, image_id)
|
||||
if ancestry_docker_ids is None:
|
||||
abort(404, 'Image %(image_id)s not found', issue='unknown-image', image_id=image_id)
|
||||
|
@ -373,6 +403,11 @@ def put_image_json(namespace, repository, image_id):
|
|||
if not permission.can():
|
||||
abort(403)
|
||||
|
||||
repo = model.get_repository(namespace, repository)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, image_id=image_id)
|
||||
|
||||
logger.debug('Parsing image JSON')
|
||||
try:
|
||||
uploaded_metadata = request.data
|
||||
|
|
|
@ -27,6 +27,11 @@ def get_tags(namespace_name, repo_name):
|
|||
permission = ReadRepositoryPermission(namespace_name, repo_name)
|
||||
|
||||
if permission.can() or model.repository_is_public(namespace_name, repo_name):
|
||||
repo = model.get_repository(namespace_name, repo_name)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, namespace=namespace_name)
|
||||
|
||||
tags = model.list_tags(namespace_name, repo_name)
|
||||
tag_map = {tag.name: tag.image.docker_image_id for tag in tags}
|
||||
return jsonify(tag_map)
|
||||
|
@ -42,6 +47,11 @@ def get_tag(namespace_name, repo_name, tag):
|
|||
permission = ReadRepositoryPermission(namespace_name, repo_name)
|
||||
|
||||
if permission.can() or model.repository_is_public(namespace_name, repo_name):
|
||||
repo = model.get_repository(namespace_name, repo_name)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, namespace=namespace_name)
|
||||
|
||||
image_id = model.find_image_id_by_tag(namespace_name, repo_name, tag)
|
||||
if image_id is None:
|
||||
abort(404)
|
||||
|
@ -64,6 +74,11 @@ def put_tag(namespace_name, repo_name, tag):
|
|||
if not TAG_REGEX.match(tag):
|
||||
abort(400, TAG_ERROR)
|
||||
|
||||
repo = model.get_repository(namespace_name, repo_name)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, namespace=namespace_name)
|
||||
|
||||
image_id = json.loads(request.data)
|
||||
model.create_or_update_tag(namespace_name, repo_name, image_id, tag)
|
||||
|
||||
|
@ -86,6 +101,11 @@ def delete_tag(namespace_name, repo_name, tag):
|
|||
permission = ModifyRepositoryPermission(namespace_name, repo_name)
|
||||
|
||||
if permission.can():
|
||||
repo = model.get_repository(namespace_name, repo_name)
|
||||
if repo.kind != 'image':
|
||||
msg = 'This repository is for managing %s resources and not container images.' % repo.kind
|
||||
abort(405, message=msg, namespace=namespace_name)
|
||||
|
||||
model.delete_tag(namespace_name, repo_name, tag)
|
||||
track_and_log('delete_tag', model.get_repository(namespace_name, repo_name), tag=tag)
|
||||
return make_response('Deleted', 200)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -154,29 +154,35 @@ def _torrent_repo_verb(repo_image, tag, verb, **kwargs):
|
|||
abort(406)
|
||||
|
||||
# Return the torrent.
|
||||
public_repo = model.repository_is_public(repo_image.repository.namespace_name,
|
||||
repo_image.repository.name)
|
||||
torrent = _torrent_for_blob(derived_image.blob, public_repo)
|
||||
repo = model.get_repository(repo_image.repository.namespace_name,
|
||||
repo_image.repository.name)
|
||||
repo_is_public = repo is not None and repo.is_public
|
||||
torrent = _torrent_for_blob(derived_image.blob, repo_is_public)
|
||||
|
||||
# Log the action.
|
||||
track_and_log('repo_verb', repo_image.repository, tag=tag, verb=verb, torrent=True, **kwargs)
|
||||
return torrent
|
||||
|
||||
|
||||
def _verify_repo_verb(_, namespace, repository, tag, verb, checker=None):
|
||||
permission = ReadRepositoryPermission(namespace, repository)
|
||||
if not permission.can() and not model.repository_is_public(namespace, repository):
|
||||
def _verify_repo_verb(_, namespace, repo_name, tag, verb, checker=None):
|
||||
permission = ReadRepositoryPermission(namespace, repo_name)
|
||||
repo = model.get_repository(namespace, repo_name)
|
||||
repo_is_public = repo is not None and repo.is_public
|
||||
if not permission.can() and not repo_is_public:
|
||||
abort(403)
|
||||
|
||||
# Lookup the requested tag.
|
||||
tag_image = model.get_tag_image(namespace, repository, tag)
|
||||
tag_image = model.get_tag_image(namespace, repo_name, tag)
|
||||
if tag_image is None:
|
||||
abort(404)
|
||||
|
||||
if repo is not None and repo.kind != 'image':
|
||||
abort(405)
|
||||
|
||||
# If there is a data checker, call it first.
|
||||
if checker is not None:
|
||||
if not checker(tag_image):
|
||||
logger.debug('Check mismatch on %s/%s:%s, verb %s', namespace, repository, tag, verb)
|
||||
logger.debug('Check mismatch on %s/%s:%s, verb %s', namespace, repo_name, tag, verb)
|
||||
abort(404)
|
||||
|
||||
return tag_image
|
||||
|
@ -345,19 +351,24 @@ def get_squashed_tag(namespace, repository, tag):
|
|||
@process_auth
|
||||
@parse_repository_name()
|
||||
def get_tag_torrent(namespace_name, repo_name, digest):
|
||||
repo = model.get_repository(namespace_name, repo_name)
|
||||
repo_is_public = repo is not None and repo.is_public
|
||||
|
||||
permission = ReadRepositoryPermission(namespace_name, repo_name)
|
||||
public_repo = model.repository_is_public(namespace_name, repo_name)
|
||||
if not permission.can() and not public_repo:
|
||||
if not permission.can() and not repo_is_public:
|
||||
abort(403)
|
||||
|
||||
user = get_authenticated_user()
|
||||
if user is None and not public_repo:
|
||||
if user is None and not repo_is_public:
|
||||
# We can not generate a private torrent cluster without a user uuid (e.g. token auth)
|
||||
abort(403)
|
||||
|
||||
if repo is not None and repo.kind != 'image':
|
||||
abort(405)
|
||||
|
||||
blob = model.get_repo_blob_by_digest(namespace_name, repo_name, digest)
|
||||
if blob is None:
|
||||
abort(404)
|
||||
|
||||
metric_queue.repository_pull.Inc(labelvalues=[namespace_name, repo_name, 'torrent', True])
|
||||
return _torrent_for_blob(blob, public_repo)
|
||||
return _torrent_for_blob(blob, repo_is_public)
|
||||
|
|
|
@ -426,9 +426,12 @@ def confirm_recovery():
|
|||
@anon_protect
|
||||
def build_status_badge(namespace_name, repo_name):
|
||||
token = request.args.get('token', None)
|
||||
repo = model.repository.get_repository(namespace_name, repo_name)
|
||||
if repo and repo.kind.name != 'image':
|
||||
abort(404)
|
||||
|
||||
is_public = model.repository.repository_is_public(namespace_name, repo_name)
|
||||
if not is_public:
|
||||
repo = model.repository.get_repository(namespace_name, repo_name)
|
||||
if not repo or token != repo.badge_token:
|
||||
abort(404)
|
||||
|
||||
|
@ -628,6 +631,8 @@ def attach_bitbucket_trigger(namespace_name, repo_name):
|
|||
if not repo:
|
||||
msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
|
||||
abort(404, message=msg)
|
||||
elif repo.kind.name != 'image':
|
||||
abort(501)
|
||||
|
||||
trigger = model.build.create_build_trigger(repo, BitbucketBuildTrigger.service_name(), None,
|
||||
current_user.db_user())
|
||||
|
@ -661,6 +666,8 @@ def attach_custom_build_trigger(namespace_name, repo_name):
|
|||
if not repo:
|
||||
msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
|
||||
abort(404, message=msg)
|
||||
elif repo.kind.name != 'image':
|
||||
abort(501)
|
||||
|
||||
trigger = model.build.create_build_trigger(repo, CustomBuildTrigger.service_name(),
|
||||
None, current_user.db_user())
|
||||
|
|
Reference in a new issue