Add flag to enable trust per repo (#2541)
* Add flag to enable trust per repo * Add api for enabling/disabling trust * Add new LogEntryKind for changing repo trust settings Also add tests for repo trust api * Add `set_trust` method to repository * Expose new logkind to UI * Fix registry tests * Rebase migrations and regen test.db * Raise downstreamissue if trust metadata can't be removed * Refactor change_repo_trust * Add show_if to change_repo_trust endpoint
This commit is contained in:
parent
aa1c8d47dd
commit
2661db7485
13 changed files with 176 additions and 12 deletions
|
@ -15,8 +15,8 @@ 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, disallow_for_app_repositories)
|
||||
from endpoints.exception import Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException
|
||||
query_param, truthy_bool, disallow_for_app_repositories, show_if)
|
||||
from endpoints.exception import Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException, DownstreamIssue
|
||||
from endpoints.api.billing import lookup_allowed_private_repos, get_namespace_plan
|
||||
from endpoints.api.subscribe import check_repository_usage
|
||||
|
||||
|
@ -377,6 +377,7 @@ class Repository(RepositoryParamResource):
|
|||
'is_organization': repo.namespace_user.organization,
|
||||
'is_starred': is_starred,
|
||||
'status_token': repo.badge_token if not is_public else '',
|
||||
'trust_enabled': repo.trust_enabled,
|
||||
}
|
||||
|
||||
if stats is not None:
|
||||
|
@ -464,3 +465,46 @@ class RepositoryVisibility(RepositoryParamResource):
|
|||
{'repo': repository, 'namespace': namespace, 'visibility': values['visibility']},
|
||||
repo=repo)
|
||||
return {'success': True}
|
||||
|
||||
|
||||
@resource('/v1/repository/<apirepopath:repository>/changetrust')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
class RepositoryTrust(RepositoryParamResource):
|
||||
""" Custom verb for changing the trust settings of the repository. """
|
||||
schemas = {
|
||||
'ChangeRepoTrust': {
|
||||
'type': 'object',
|
||||
'description': 'Change the trust settings for the repository.',
|
||||
'required': [
|
||||
'trust_enabled',
|
||||
],
|
||||
'properties': {
|
||||
'trust_enabled': {
|
||||
'type': 'boolean',
|
||||
'description': 'Whether or not signing is enabled for the repository.'
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@show_if(features.SIGNING)
|
||||
@require_repo_admin
|
||||
@nickname('changeRepoTrust')
|
||||
@validate_json_request('ChangeRepoTrust')
|
||||
def post(self, namespace, repository):
|
||||
""" Change the visibility of a repository. """
|
||||
repo = model.repository.get_repository(namespace, repository)
|
||||
if not repo:
|
||||
raise NotFound()
|
||||
|
||||
if not tuf_metadata_api.delete_metadata(namespace, repository):
|
||||
raise DownstreamIssue({'message': 'Unable to delete downstream trust metadata'})
|
||||
|
||||
values = request.get_json()
|
||||
model.repository.set_trust(repo, values['trust_enabled'])
|
||||
|
||||
log_action('change_repo_trust', namespace,
|
||||
{'repo': repository, 'namespace': namespace, 'trust_enabled': values['trust_enabled']},
|
||||
repo=repo)
|
||||
|
||||
return {'success': True}
|
||||
|
|
42
endpoints/api/test/test_repository.py
Normal file
42
endpoints/api/test/test_repository.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
import pytest
|
||||
|
||||
from endpoints.api.test.shared import client_with_identity, conduct_api_call
|
||||
from endpoints.api.repository import RepositoryTrust
|
||||
from test.fixtures import app, appconfig, database_uri, init_db_path, sqlitedb_file
|
||||
from mock import patch, ANY, MagicMock
|
||||
|
||||
|
||||
INVALID_RESPONSE = {
|
||||
u'detail': u"u'invalid_req' is not of type 'boolean'",
|
||||
u'error_message': u"u'invalid_req' is not of type 'boolean'",
|
||||
u'error_type': u'invalid_request',
|
||||
u'status': 400,
|
||||
u'title': u'invalid_request',
|
||||
u'type': u'http://localhost/api/v1/error/invalid_request'
|
||||
}
|
||||
|
||||
NOT_FOUND_RESPONSE = {
|
||||
u'detail': u'Not Found',
|
||||
u'error_message': u'Not Found',
|
||||
u'error_type': u'not_found',
|
||||
u'message': u'You have requested this URI [/api/v1/repository/devtable/repo/changetrust] but did you mean /api/v1/repository/<apirepopath:repository>/changetrust or /api/v1/repository/<apirepopath:repository>/changevisibility or /api/v1/repository/<apirepopath:repository>/tag/<tag>/images ?',
|
||||
u'status': 404,
|
||||
u'title': u'not_found',
|
||||
u'type': u'http://localhost/api/v1/error/not_found'
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trust_enabled,repo_found,expected_body,expected_status', [
|
||||
(True, True,{'success': True}, 200),
|
||||
(False, True, {'success': True}, 200),
|
||||
(False, False, NOT_FOUND_RESPONSE, 404),
|
||||
('invalid_req', False, INVALID_RESPONSE , 400),
|
||||
])
|
||||
def test_post_changetrust(trust_enabled, repo_found, expected_body, expected_status, client):
|
||||
with patch('endpoints.api.repository.tuf_metadata_api'):
|
||||
with patch('endpoints.api.repository.model') as mock_model:
|
||||
mock_model.repository.get_repository.return_value = MagicMock() if repo_found else None
|
||||
with client_with_identity('devtable', client) as cl:
|
||||
params = {'repository': 'devtable/repo'}
|
||||
request_body = {'trust_enabled': trust_enabled}
|
||||
assert expected_body == conduct_api_call(cl, RepositoryTrust, 'POST', params, request_body, expected_status).json
|
|
@ -7,6 +7,7 @@ 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.api.signing import RepositorySignatures
|
||||
from endpoints.api.repository import RepositoryTrust
|
||||
from test.fixtures import app, appconfig, database_uri, init_db_path, sqlitedb_file
|
||||
|
||||
TEAM_PARAMS = {'orgname': 'buynlarge', 'teamname': 'owners'}
|
||||
|
@ -42,6 +43,11 @@ REPO_PARAMS = {'repository': 'devtable/someapp'}
|
|||
(RepositorySignatures, 'GET', REPO_PARAMS, {}, 'freshuser', 403),
|
||||
(RepositorySignatures, 'GET', REPO_PARAMS, {}, 'reader', 403),
|
||||
(RepositorySignatures, 'GET', REPO_PARAMS, {}, 'devtable', 200),
|
||||
|
||||
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, None, 403),
|
||||
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'freshuser', 403),
|
||||
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'reader', 403),
|
||||
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'devtable', 404),
|
||||
])
|
||||
def test_api_security(resource, method, params, body, identity, expected, client):
|
||||
with client_with_identity(identity, client) as cl:
|
||||
|
|
Reference in a new issue