diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index 12584e70e..873e21a9a 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -416,4 +416,5 @@ import endpoints.api.team import endpoints.api.trigger import endpoints.api.user import endpoints.api.secscan +import endpoints.api.signing diff --git a/endpoints/api/repository.py b/endpoints/api/repository.py index 8cea8a602..37f22b856 100644 --- a/endpoints/api/repository.py +++ b/endpoints/api/repository.py @@ -378,7 +378,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, + 'trust_enabled': bool(features.SIGNING) and repo.trust_enabled, } if stats is not None: diff --git a/endpoints/api/signing.py b/endpoints/api/signing.py index aa23b7062..161f87760 100644 --- a/endpoints/api/signing.py +++ b/endpoints/api/signing.py @@ -4,9 +4,10 @@ import logging import features from app import tuf_metadata_api +from data import model from endpoints.api import (require_repo_read, path_param, - RepositoryParamResource, resource, nickname, show_if, - disallow_for_app_repositories) + RepositoryParamResource, resource, nickname, show_if, + disallow_for_app_repositories, NotFound) logger = logging.getLogger(__name__) @@ -21,7 +22,11 @@ class RepositorySignatures(RepositoryParamResource): @nickname('getRepoSignatures') @disallow_for_app_repositories def get(self, namespace, repository): - """ Fetches the list of signed tags for the repository""" + """ Fetches the list of signed tags for the repository. """ + repo = model.repository.get_repository(namespace, repository) + if repo is None or not repo.trust_enabled: + raise NotFound() + tag_data, expiration = tuf_metadata_api.get_default_tags_with_expiration(namespace, repository) return { 'tags': tag_data, diff --git a/endpoints/api/test/test_repository.py b/endpoints/api/test/test_repository.py index f686fd9e4..e3b3050b8 100644 --- a/endpoints/api/test/test_repository.py +++ b/endpoints/api/test/test_repository.py @@ -1,7 +1,8 @@ import pytest from endpoints.api.test.shared import client_with_identity, conduct_api_call -from endpoints.api.repository import RepositoryTrust +from endpoints.api.repository import RepositoryTrust, Repository +from features import FeatureNameValue from test.fixtures import app, appconfig, database_uri, init_db_path, sqlitedb_file from mock import patch, ANY, MagicMock @@ -40,3 +41,11 @@ def test_post_changetrust(trust_enabled, repo_found, expected_body, expected_sta 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 + + +def test_signing_disabled(client): + with patch('features.SIGNING', FeatureNameValue('SIGNING', False)): + with client_with_identity('devtable', client) as cl: + params = {'repository': 'devtable/simple'} + response = conduct_api_call(cl, Repository, 'GET', params).json + assert not response['trust_enabled'] diff --git a/endpoints/api/test/test_security.py b/endpoints/api/test/test_security.py index bfcae8b99..a65ca7b0e 100644 --- a/endpoints/api/test/test_security.py +++ b/endpoints/api/test/test_security.py @@ -39,11 +39,11 @@ REPO_PARAMS = {'repository': 'devtable/someapp'} (SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'freshuser', 403), (SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'reader', 403), (SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'devtable', 404), - + (RepositorySignatures, 'GET', REPO_PARAMS, {}, 'freshuser', 403), (RepositorySignatures, 'GET', REPO_PARAMS, {}, 'reader', 403), - (RepositorySignatures, 'GET', REPO_PARAMS, {}, 'devtable', 200), - + (RepositorySignatures, 'GET', REPO_PARAMS, {}, 'devtable', 404), + (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), diff --git a/endpoints/api/test/test_signing.py b/endpoints/api/test/test_signing.py index 93ac94be6..056fdad7f 100644 --- a/endpoints/api/test/test_signing.py +++ b/endpoints/api/test/test_signing.py @@ -30,7 +30,7 @@ def tags_equal(expected, actual): return expected == actual @pytest.mark.parametrize('targets,expected', [ - (VALID_TARGETS, {'tags': VALID_TARGETS, 'expiration': 'expires'}), + (VALID_TARGETS, {'tags': VALID_TARGETS, 'expiration': 'expires'}), ({'bad': 'tags'}, {'tags': {'bad': 'tags'}, 'expiration': 'expires'}), ({}, {'tags': {}, 'expiration': 'expires'}), (None, {'tags': None, 'expiration': 'expires'}), # API returns None on exceptions @@ -39,5 +39,5 @@ def test_get_signatures(targets, expected, client): with patch('endpoints.api.signing.tuf_metadata_api') as mock_tuf: mock_tuf.get_default_tags_with_expiration.return_value = (targets, 'expires') with client_with_identity('devtable', client) as cl: - params = {'repository': 'devtable/repo'} + params = {'repository': 'devtable/trusted'} assert tags_equal(expected, conduct_api_call(cl, RepositorySignatures, 'GET', params, None, 200).json) diff --git a/initdb.py b/initdb.py index efcb99da8..28093dcfd 100644 --- a/initdb.py +++ b/initdb.py @@ -576,6 +576,11 @@ def populate_database(minimal=False, with_storage=False): (1, [(1, [], 'v5.0'), (1, [], 'v6.0')], None)], None)) + trusted_repo = __generate_repository(with_storage, new_user_1, 'trusted', 'Trusted repository.', + False, [], (4, [], ['latest', 'prod'])) + trusted_repo.trust_enabled = True + trusted_repo.save() + publicrepo = __generate_repository(with_storage, new_user_2, 'publicrepo', 'Public repository pullable by the world.', True, [], (10, [], 'latest')) diff --git a/static/css/core-ui.css b/static/css/core-ui.css index 36b2ce342..1c4672081 100644 --- a/static/css/core-ui.css +++ b/static/css/core-ui.css @@ -1170,6 +1170,10 @@ a:focus { visibility: hidden; } +.co-table thead td.unorderable-col:after { + display: none; +} + .co-table thead td.current:after { content: "\f175"; visibility: visible; diff --git a/static/css/directives/repo-view/repo-panel-tags.css b/static/css/directives/repo-view/repo-panel-tags.css index 0cb53a633..7e03596bf 100644 --- a/static/css/directives/repo-view/repo-panel-tags.css +++ b/static/css/directives/repo-view/repo-panel-tags.css @@ -81,6 +81,10 @@ margin-right: 2px; } +.repo-panel-tags-element .signing-col { + text-align: center; +} + .repo-panel-tags-element .security-scan-col span { cursor: pointer; } @@ -179,4 +183,9 @@ .repo-panel-tags-element .co-checked-actions .btn .text { display: none; } +} + +.repo-panel-tags-element .disabled-option, +.repo-panel-tags-element .disabled-option a { + color: #ccc; } \ No newline at end of file diff --git a/static/css/directives/ui/repository-signing-config.css b/static/css/directives/ui/repository-signing-config.css new file mode 100644 index 000000000..98fafb4f3 --- /dev/null +++ b/static/css/directives/ui/repository-signing-config.css @@ -0,0 +1,16 @@ +.repository-signing-config-element td { + vertical-align: top; +} + +.repository-signing-config-element .status-icon { + font-size: 48px; + margin-right: 10px; +} + +.repository-signing-config-element .status-icon.ci-shield-check-outline { + color: #2FC98E; +} + +.repository-signing-config-element .status-icon.ci-shield-none { + color: #9B9B9B; +} \ No newline at end of file diff --git a/static/css/directives/ui/tag-signing-display.css b/static/css/directives/ui/tag-signing-display.css new file mode 100644 index 000000000..d05d4608b --- /dev/null +++ b/static/css/directives/ui/tag-signing-display.css @@ -0,0 +1,55 @@ +.tag-signing-display-element { + text-align: center; + display: inline-block; + cursor: default; +} + +.tag-signing-display-element .fa { + font-size: 18px; +} + +.tag-signing-display-element .fa.fa-question-circle { + font-size: 14px; + line-height: 18px; + text-align: center; +} + +.tag-signing-display-element .signing-load-error { + color: #CCC; +} + +.tag-signing-display-element .signing-not-signed { + color: #9B9B9B; +} + +.tag-signing-display-element .signing-valid .okay, +.tag-signing-display-element .signing-valid .expires-soon { + color: #2FC98E; +} + + +.tag-signing-display-element .signing-valid .expires-soon { + position: relative; +} + +.tag-signing-display-element .signing-valid .expires-soon:after { + border-radius: 50%; + width: 6px; + height: 6px; + position: absolute; + bottom: 0px; + right: 0px; + z-index: 1; + display: inline-block; + content: " "; + background-color: #FCA657; +} + + +.tag-signing-display-element .signing-valid .expired { + color: #FCA657; +} + +.tag-signing-display-element .signing-invalid { + color: #D64456; +} \ No newline at end of file diff --git a/static/directives/header-bar.html b/static/directives/header-bar.html index 63c72c91a..713f2bcbb 100644 --- a/static/directives/header-bar.html +++ b/static/directives/header-bar.html @@ -96,12 +96,14 @@ New Robot Account -
+