Merge pull request #2653 from coreos-inc/new-signing-ui

Implement updated UI for displaying the signing status of a tag, now …
This commit is contained in:
josephschorr 2017-05-24 11:31:52 -04:00 committed by GitHub
commit 8e8470890a
12 changed files with 433 additions and 128 deletions

View file

@ -27,9 +27,4 @@ class RepositorySignatures(RepositoryParamResource):
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,
'expiration': expiration
}
return {'delegations': tuf_metadata_api.get_all_tags_with_expiration(namespace, repository)}

View file

@ -8,37 +8,47 @@ from endpoints.api.signing import RepositorySignatures
from test.fixtures import *
VALID_TARGETS = {
'latest': {
'hashes': {
'sha256': 'mLmxwTyUrqIRDaz8uaBapfrp3GPERfsDg2kiMujlteo='
},
'length': 1500
},
'test_tag': {
'hashes': {
'sha256': '1234123'
},
'length': 50
VALID_TARGETS_MAP = {
"targets/ci": {
"targets": {
"latest": {
"hashes": {
"sha256": "2Q8GLEgX62VBWeL76axFuDj/Z1dd6Zhx0ZDM6kNwPkQ="
},
"length": 2111
}
},
"expiration": "2020-05-22T10:26:46.618176424-04:00"
},
"targets": {
"targets": {
"latest": {
"hashes": {
"sha256": "2Q8GLEgX62VBWeL76axFuDj/Z1dd6Zhx0ZDM6kNwPkQ="
},
"length": 2111
}
},
"expiration": "2020-05-22T10:26:01.953414888-04:00"}
}
}
def tags_equal(expected, actual):
expected_tags = expected.get('tags')
actual_tags = actual.get('tags')
expected_tags = expected.get('delegations')
actual_tags = actual.get('delegations')
if expected_tags and actual_tags:
return Counter(expected_tags) == Counter(actual_tags)
return expected == actual
@pytest.mark.parametrize('targets,expected', [
(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
@pytest.mark.parametrize('targets_map,expected', [
(VALID_TARGETS_MAP, {'delegations': VALID_TARGETS_MAP}),
({'bad': 'tags'}, {'delegations': {'bad': 'tags'}}),
({}, {'delegations': {}}),
(None, {'delegations': None}), # API returns None on exceptions
])
def test_get_signatures(targets, expected, client):
def test_get_signatures(targets_map, expected, client):
with patch('endpoints.api.signing.tuf_metadata_api') as mock_tuf:
mock_tuf.get_default_tags_with_expiration.return_value = (targets, 'expires')
mock_tuf.get_all_tags_with_expiration.return_value = targets_map
with client_with_identity('devtable', client) as cl:
params = {'repository': 'devtable/trusted'}
assert tags_equal(expected, conduct_api_call(cl, RepositorySignatures, 'GET', params, None, 200).json)

View file

@ -43,7 +43,7 @@
.repo-panel-tags-element .image-track-line.start {
top: 18px;
height: 25px;
height: 28px;
display: block;
}
@ -144,6 +144,10 @@
padding-top: 0px;
}
.repo-panel-tags-element .signing-delegations-list {
margin-top: 8px;
}
@media (max-width: 1000px) {
.repo-panel-tags-element .image-track {
display: none;

View file

@ -1,5 +1,17 @@
.manifest-label-list-element {
padding-left: 6px;
display: inline-block;
position: relative;
}
.manifest-label-list-element:before {
content: "\f02c";
font-family: FontAwesome;
position: absolute;
left: -22px;
top: 0px;
font-size: 15px;
color: #888;
}
.manifest-label-list-element .none {

View file

@ -2,10 +2,11 @@
text-align: center;
display: inline-block;
cursor: default;
position: relative;
}
.tag-signing-display-element .fa {
font-size: 18px;
font-size: 24px;
}
.tag-signing-display-element .fa.fa-question-circle {
@ -22,34 +23,153 @@
color: #9B9B9B;
}
.tag-signing-display-element .signing-valid .okay,
.tag-signing-display-element .signing-valid .expires-soon {
.tag-signing-display-element .signing-valid.okay-release {
color: #2FC98E;
}
.tag-signing-display-element .signing-valid .expires-soon {
position: relative;
.tag-signing-display-element .signing-valid.okay {
color: #5f9dd0;
}
.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 {
.tag-signing-display-element .signing-valid.partial-okay {
color: #FCA657;
}
.tag-signing-display-element .signing-invalid {
color: #D64456;
}
}
.tag-signing-display-element .indicator {
position: relative;
}
.tag-signing-display-element .expiring-soon {
border-radius: 100%;
background-color: #fbab62;
position: absolute;
right: 0px;
bottom: 0px;
width: 8px;
height: 8px;
z-index: 2;
}
.tag-signing-display-element .expired {
border-radius: 100%;
background-color: #ec5266;
position: absolute;
right: 0px;
bottom: 0px;
width: 8px;
height: 8px;
z-index: 3;
}
.tag-signing-display-element .invalid {
border-radius: 100%;
background-color: #ec5266;
position: absolute;
right: 0px;
bottom: 0px;
width: 8px;
height: 8px;
z-index: 4;
}
.tag-signing-display-element.extended {
display: block;
position: relative;
}
.tag-signing-display-element.extended .fa {
color: #888 !important;
font-size: 16px;
}
.tag-signing-display-element.extended .indicator {
font-size: 16px;
position: absolute;
left: -22px;
top: 4px;
}
.tag-signing-display-element.extended .delegations {
margin: 0px;
padding: 0px;
text-align: left;
padding-left: 6px;
padding-top: 6px;
}
.tag-signing-display-element.extended .delegations td {
padding: 4px;
border: 0px;
}
.tag-signing-display-element.extended .delegations .delegation {
padding: 4px;
background-color: #eee;
border-radius: 4px;
font-size: 13px;
padding-right: 6px;
display: inline-block;
}
.tag-signing-display-element.extended .delegations .delegation:before {
content: "\f00c";
font-family: FontAwesome;
margin-right: 2px;
margin-left: 2px;
display: inline-block;
font-size: 10px;
}
.tag-signing-display-element.extended .delegations .delegation.okay {
background-color: #d0deea;
}
.tag-signing-display-element.extended .delegations .delegation.okay:before {
color: #5f9dd0;
}
.tag-signing-display-element.extended .delegations .delegation.default {
background-color: #bdf1dd;
}
.tag-signing-display-element.extended .delegations .delegation.default:before {
color: #2FC98E;
}
.tag-signing-display-element.extended .delegations .delegation.warning {
background-color: #ffe0c4;
}
.tag-signing-display-element.extended .delegations .delegation.warning:before {
content: "\f12a";
color: #FCA657;
}
.tag-signing-display-element.extended .delegations .delegation.error {
background-color: #ffcad1;
}
.tag-signing-display-element.extended .delegations .delegation.error:before {
content: "\f00d";
color: #D64456;
}
.tag-signing-display-element.extended .delegations .delegation-name {
font-size: 14px;
}
.tag-signing-display-element.extended .delegations .delegation-info {
display: inline-block;
white-space: nowrap;
vertical-align: middle;
margin-left: 4px;
font-size: 12px;
}

View file

@ -131,7 +131,7 @@
<td class="signing-col hidden-xs"
quay-require="['SIGNING']"
ng-if="repository.trust_enabled">
<tag-signing-display tag="tag" signatures="repoSignatureInfo"></tag-signing-display>
<tag-signing-display tag="tag" delegations="repoDelegationsInfo" compact="true"></tag-signing-display>
</td>
<td class="hidden-xs">
<span bo-if="tag.last_modified" data-title="{{ tag.last_modified | amDateFormat:'dddd, MMMM Do YYYY, h:mm:ss a' }}" bs-tooltip>
@ -261,9 +261,15 @@
</tr>
<tr ng-if="expandedView">
<td class="checkbox-col"></td>
<td class="labels-col" colspan="{{5 + (Features.SECURITY_SCANNER ? 1 : 0)}}">
<td class="labels-col" colspan="{{5 + (Features.SECURITY_SCANNER ? 1 : 0) + (repository.trust_enabled ? 1 : 0) }}">
<!-- Labels -->
<div class="manifest-label-list" repository="repository"
manifest-digest="tag.manifest_digest" cache="labelCache"></div>
<!-- Delegations -->
<div class="signing-delegations-list" ng-if="repository.trust_enabled">
<tag-signing-display compact="false" tag="tag" delegations="repoDelegationsInfo"></tag-signing-display>
</div>
</td>
<td class="hidden-xs hidden-sm image-track" ng-repeat="it in imageTracks"
ng-if="imageTracks.length <= maxTrackCount" bindonce>

View file

@ -39,7 +39,7 @@ angular.module('quay').directive('repoPanelTags', function () {
$scope.labelCache = {};
$scope.imageVulnerabilities = {};
$scope.repoSignatureInfo = null;
$scope.repoDelegationsInfo = null;
$scope.defcon1 = {};
$scope.hasDefcon1 = false;
@ -50,16 +50,16 @@ angular.module('quay').directive('repoPanelTags', function () {
}
$scope.repoSignatureError = false;
$scope.repoSignatureInfo = null;
$scope.repoDelegationsInfo = null;
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
ApiService.getRepoSignatures(null, params).then(function(resp) {
$scope.repoSignatureInfo = resp;
$scope.repoDelegationsInfo = resp;
}, function() {
$scope.repoSignatureInfo = {'error': true};
$scope.repoDelegationsInfo = {'error': true};
});
};

View file

@ -1,5 +1,5 @@
<span class="tag-signing-display-element">
<span ng-switch on="$ctrl.signingStatus($ctrl.tag, $ctrl.signatures)">
<span class="tag-signing-display-element" ng-class="{'extended': !$ctrl.compact}">
<span class="indicator" ng-switch on="$ctrl.signingStatus($ctrl.tag, $ctrl.delegations)">
<!-- Loading -->
<span ng-switch-when="loading">
<span class="cor-loader-inline"></span>
@ -17,33 +17,81 @@
<i class="fa shield-icon ci-shield-none"></i>
</span>
<!-- Signature Valid -->
<span class="signing-valid" ng-switch-when="valid-signature">
<span ng-switch on="$ctrl.expirationStatus($ctrl.tag, $ctrl.signatures)">
<!-- But expired -->
<span class="expired" ng-switch-when="expired"
data-title="This tag has a matching, but expired signature" bs-tooltip>
<i class="fa shield-icon ci-shield-invalid-outline"></i>
</span>
<!-- Releases + all other targets signed -->
<span class="signing-valid okay-release" ng-switch-when="all-signed"
data-title="The tag has valid and matching signatures" bs-tooltip>
<i class="fa shield-icon ci-shield-check-full"></i>
<!-- Expires soon -->
<span class="expires-soon" ng-switch-when="expires-soon"
data-title="This tag has a valid and matching signature, but it is expiring soon on {{ $ctrl.signatures.expiration }}" bs-tooltip>
<i class="fa shield-icon ci-shield-check-outline"></i>
</span>
<!-- Okay -->
<span class="okay" ng-switch-when="okay"
data-title="This tag has a valid and matching signature" bs-tooltip>
<i class="fa shield-icon ci-shield-check-outline"></i>
</span>
</span>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasExpiringSoon" class="expiring-soon"></span>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasExpired" class="expired"></span>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasInvalid" class="invalid"></span>
</span>
<!-- Signature Invalid -->
<span class="signing-invalid" ng-switch-when="invalid-signature"
data-title="The signed digest for this tag does not match the one pushed.<br><br>Signed: {{ this.signedDigest.substr(0, 12) }}<br>Pushed: {{ this.pushedDigest.substr(0, 12) }}" data-html="true" bs-tooltip>
<!-- Releases target signed -->
<span class="signing-valid okay-release" ng-switch-when="release-signed"
data-title="The tag has a valid and matching default signature" bs-tooltip>
<i class="fa shield-icon ci-shield-check-outline"></i>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasExpiringSoon" class="expiring-soon"></span>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasExpired" class="expired"></span>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasInvalid" class="invalid"></span>
</span>
<!-- Non-releases target signed -->
<span class="signing-valid okay" ng-switch-when="non-release-signed"
data-title="The tag has valid and matching non-default signatures" bs-tooltip>
<i class="fa shield-icon ci-shield-check-outline"></i>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasExpiringSoon" class="expiring-soon"></span>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasExpired" class="expired"></span>
<span ng-if="$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).hasInvalid" class="invalid"></span>
</span>
<!-- Non-releases target signed -->
<span class="signing-valid partial-okay" ng-switch-when="one-valid-signed"
data-title="The tag has at least one valid and matching non-default signature, but some are invalid or have expired" bs-tooltip>
<i class="fa shield-icon ci-shield-check-outline"></i>
</span>
<!-- All signatures are invalid -->
<span class="signing-invalid" ng-switch-when="invalid-signed"
data-title="There are no valid or non-expired signatures for this tag" bs-tooltip>
<i class="fa shield-icon ci-shield-invalid-outline"></i>
</span>
</span>
<!-- Delegations -->
<div class="delegations" ng-if="!$ctrl.compact && !$ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).delegations.length">
This tag has not been signed
</div>
<table class="delegations" ng-if="!$ctrl.compact && $ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).delegations.length">
<tr ng-repeat="delegation in $ctrl.getSigningInfo($ctrl.tag, $ctrl.delegations).delegations">
<td>
<span class="delegation" ng-class="{'default': $ctrl.isDefaultDelegation(delegation.delegationName), 'okay': delegation.hasMatchingHash && !delegation.isExpired && !delegation.isExpiringSoon, 'warning': delegation.hasMatchingHash && delegation.isExpiringSoon, 'error': !delegation.hasMatchingHash || delegation.isExpired}">
<span class="delegation-name" ng-if="delegation.delegationName == 'targets'">(Default)</span>
<span class="delegation-name" ng-if="delegation.delegationName != 'targets'">{{ delegation.delegationName.substr('targets/'.length) }}</span>
</span>
<div class="delegation-info visible-xs" ng-if="!delegation.hasMatchingHash || delegation.isExpiringSoon || delegation.isExpired">
<span class="failure-reason" ng-if="delegation.hasMatchingHash && delegation.isExpiringSoon">
Expiring soon: {{ delegation.expiration.format("YYYY-MM-DD HH:mm:ss") }}
</span>
<span class="failure-reason" ng-if="delegation.hasMatchingHash && delegation.isExpired">Signature expired</span>
<span class="failure-reason" ng-if="!delegation.hasMatchingHash">
Signature has different hash: {{ delegation.delegationHash.substr(0, 12) }}
</span>
</div>
</td>
<td class="hidden-xs">
<span class="delegation-info" ng-if="!delegation.hasMatchingHash || delegation.isExpiringSoon || delegation.isExpired">
<span class="failure-reason" ng-if="delegation.hasMatchingHash && delegation.isExpiringSoon">
Expiring soon: {{ delegation.expiration.format("YYYY-MM-DD HH:mm:ss") }}
</span>
<span class="failure-reason" ng-if="delegation.hasMatchingHash && delegation.isExpired">Signature expired</span>
<span class="failure-reason" ng-if="!delegation.hasMatchingHash">
Signature has different hash: {{ delegation.delegationHash.substr(0, 12) }}
</span>
</span>
</td>
</tr>
</table>
</span>

View file

@ -1,7 +1,27 @@
import { Input, Component, Inject } from 'ng-metadata/core';
import { ApostilleSignatureDocument, ApostilleTagDocument } from '../../../types/common.types';
import { ApostilleDelegationsSet, ApostilleSignatureDocument, ApostilleTagDocument } from '../../../types/common.types';
import * as moment from "moment";
type TagSigningInfo = {
delegations: DelegationInfo[];
delegationsByName: {[delegationName: string]: DelegationInfo};
hasExpiringSoon: boolean;
hasExpired: boolean;
hasInvalid: boolean;
}
type DelegationInfo = {
delegationName: string;
delegationHash: string;
expiration: moment.Moment;
hasMatchingHash: boolean;
isExpired: boolean;
isExpiringSoon: boolean
};
var RELEASES = ['targets/releases', 'targets'];
/**
* A component that displays the signing status of a tag in the repository view.
*/
@ -10,17 +30,22 @@ import * as moment from "moment";
templateUrl: '/static/js/directives/ui/tag-signing-display/tag-signing-display.component.html',
})
export class TagSigningDisplayComponent {
@Input('<') public compact: boolean;
@Input('<') public tag: any;
@Input('<') public signatures: ApostilleSignatureDocument;
@Input('<') public delegations: ApostilleDelegationsSet;
private signedDigest: string;
private pushedDigest: string;
private cachedSigningInfo: TagSigningInfo | null = null;
constructor(@Inject("$sanitize") private $sanitize: ng.sanitize.ISanitizeService) {}
private base64ToHex(base64String: string): string {
// Based on: http://stackoverflow.com/questions/39460182/decode-base64-to-hexadecimal-string-with-javascript
var raw = atob(base64String);
try {
var raw = atob(base64String);
} catch (e) {
return '(invalid)';
}
var hexString = '';
for (var i = 0; i < raw.length; ++i) {
var char = raw.charCodeAt(i);
@ -30,49 +55,122 @@ export class TagSigningDisplayComponent {
return hexString;
}
private expirationStatus(tag: any, signatures: ApostilleSignatureDocument): string {
if (!signatures || !signatures.expiration) {
return 'unknown';
}
private buildDelegationInfo(tag: any, delegationName: string, delegation: ApostilleSignatureDocument): DelegationInfo {
var digest_without_prefix = tag.manifest_digest.substr('sha256:'.length);
var hex_signature = this.base64ToHex(delegation.targets[tag.name].hashes['sha256']);
var expires = moment(signatures.expiration);
var expires = moment(delegation.expiration);
var now = moment();
if (expires.isSameOrBefore(now)) {
return 'expired';
}
var withOneWeek = moment().add('1', 'w');
if (expires.isSameOrBefore(withOneWeek)) {
return 'expires-soon';
}
return 'okay';
return {
'delegationName': delegationName,
'hasMatchingHash': digest_without_prefix == hex_signature,
'expiration': expires,
'delegationHash': hex_signature,
'isExpired': expires.isSameOrBefore(now),
'isExpiringSoon': !expires.isSameOrBefore(now) && expires.isSameOrBefore(withOneWeek),
}
}
private signingStatus(tag: any, signatures: ApostilleSignatureDocument): string {
if (!tag || !signatures) {
private buildTagSigningInfo(tag: any, delegationSet: ApostilleDelegationsSet): TagSigningInfo {
var info = {
'delegations': [],
'delegationsByName': {},
'hasExpired': false,
'hasExpiringSoon': false,
'hasInvalid': false,
}
// Find all delegations containing the tag as a target.
Object.keys(delegationSet.delegations).forEach((delegationName) => {
var delegation = delegationSet.delegations[delegationName];
if (delegation.targets[tag.name]) {
var DelegationInfo = this.buildDelegationInfo(tag, delegationName, delegation)
info.delegations.push(DelegationInfo);
info.delegationsByName[delegationName] = DelegationInfo;
if (DelegationInfo.isExpired) {
info.hasExpired = true;
}
if (DelegationInfo.isExpiringSoon) {
info.hasExpiringSoon = true;
}
if (!DelegationInfo.hasMatchingHash) {
info.hasInvalid = true;
}
}
});
return info;
}
private isDefaultDelegation(name: string): boolean {
return RELEASES.indexOf(name) >= 0;
}
private getSigningInfo(tag: any, delegationSet: ApostilleDelegationsSet): TagSigningInfo {
if (!this.cachedSigningInfo) {
this.cachedSigningInfo = this.buildTagSigningInfo(tag, delegationSet);
}
return this.cachedSigningInfo;
}
private signingStatus(tag: any, delegationSet: ApostilleDelegationsSet): string {
if (!tag || !delegationSet) {
return 'loading';
}
if (signatures.error || !signatures.tags) {
return 'error';
}
var tag_info = signatures.tags[tag.name];
if (!tag_info || !tag.manifest_digest) {
if (!tag.manifest_digest) {
return 'not-signed';
}
var digest_without_prefix = tag.manifest_digest.substr('sha256:'.length);
var hex_signature = this.base64ToHex(tag_info.hashes['sha256']);
if (hex_signature == digest_without_prefix) {
return 'valid-signature';
} else {
this.signedDigest = this.$sanitize(hex_signature);
this.pushedDigest = this.$sanitize(digest_without_prefix);
return 'invaid-signature';
if (delegationSet.error) {
return 'error';
}
// Check if signed at all.
var signingInfo = this.getSigningInfo(tag, delegationSet);
if (!signingInfo.delegations.length) {
return 'not-signed';
}
// Check if all delegations are signed and valid.
var allReleasesValid = true;
var oneReleaseValid = false;
this.cachedSigningInfo.delegations.forEach(function(delegation) {
var isValid = delegation.hasMatchingHash && !delegation.isExpired;
if (isValid) {
oneReleaseValid = true;
}
allReleasesValid = allReleasesValid && isValid;
});
// Check if the special RELEASES target(s) is/are signed and valid.
var releasesDelegation = null;
RELEASES.forEach((releaseTarget) => {
var delegation = this.cachedSigningInfo.delegationsByName[releaseTarget];
if (delegation && !releasesDelegation) {
releasesDelegation = delegation;
}
});
if (releasesDelegation && releasesDelegation.hasMatchingHash && !releasesDelegation.isExpired) {
if (allReleasesValid && this.cachedSigningInfo.delegations.length > 1) {
return 'all-signed';
} else {
return 'release-signed';
}
}
if (allReleasesValid || oneReleaseValid) {
return 'non-release-signed';
}
return 'invalid-signed';
}
}

View file

@ -122,6 +122,16 @@ export type TriggerConfig = {
};
/**
* Represents a set of apostille delegations.
*/
export type ApostilleDelegationsSet = {
delegations: {[delegationName: string]: ApostilleSignatureDocument};
// The error that occurred, if any.
error: string | null;
};
/**
* Represents an apostille signature document, with extra expiration information.
*/
@ -130,10 +140,7 @@ export type ApostilleSignatureDocument = {
expiration: string
// Object of information for each tag.
tags: {string: ApostilleTagDocument}
// If true, an error occurred while trying to load this document.
error: boolean
targets: {string: ApostilleTagDocument}
};

View file

@ -11,7 +11,7 @@ from data.database import CloseForLongOperation
from util.abchelpers import nooper
from util.failover import failover, FailoverException
from util.security.instancekeys import InstanceKeys
from util.security.registry_jwt import build_context_and_subject, generate_bearer_token, QUAY_TUF_ROOT
from util.security.registry_jwt import build_context_and_subject, generate_bearer_token, QUAY_TUF_ROOT, SIGNER_TUF_ROOT
DEFAULT_HTTP_HEADERS = {'Connection': 'close'}
@ -150,16 +150,21 @@ class ImplementedTUFMetadataAPI(TUFMetadataAPIInterface):
if not targets_file:
targets_file = 'targets.json'
targets_name = targets_file
if targets_name.endswith('.json'):
targets_name = targets_name[:-5]
if not targets_map:
targets_map = {}
signed = self._get_signed(namespace, repository, targets_file)
if not signed:
return None
targets_map[targets_name] = None
return targets_map
if signed.get('targets'):
targets_map[targets_file] = {
targets_map[targets_name] = {
'targets': signed.get('targets'),
'expiration': signed.get('expires'),
}
@ -167,7 +172,7 @@ class ImplementedTUFMetadataAPI(TUFMetadataAPIInterface):
delegation_names = [role.get('name') for role in signed.get('delegations').get('roles')]
for delegation in delegation_names:
targets_map = self.get_all_tags_with_expiration(namespace, repository, targets_file=delegation, targets_map=targets_map)
targets_map = self.get_all_tags_with_expiration(namespace, repository, targets_file=delegation + '.json', targets_map=targets_map)
return targets_map
@ -235,7 +240,7 @@ class ImplementedTUFMetadataAPI(TUFMetadataAPIInterface):
'name': gun,
'actions': actions,
}]
context, subject = build_context_and_subject(user=None, token=None, oauthtoken=None, tuf_root=QUAY_TUF_ROOT)
context, subject = build_context_and_subject(user=None, token=None, oauthtoken=None, tuf_root=SIGNER_TUF_ROOT)
token = generate_bearer_token(self._config["SERVER_HOSTNAME"], subject, context, access,
TOKEN_VALIDITY_LIFETIME_S, self._instance_keys)
return {'Authorization': 'Bearer %s' % token}

View file

@ -177,7 +177,7 @@ def test_get_default_tags(response_code, response_body, expected):
(200, valid_targets_with_delegation, valid_delegation, {
'targets/devs': { 'targets': valid_delegation['signed']['targets'],
'expiration': valid_delegation['signed']['expires']}}),
(200, {'garbage': 'data'}, {'garbage': 'data'}, None)
(200, {'garbage': 'data'}, {'garbage': 'data'}, {'targets': None})
])
def test_get_all_tags(response_code, response_body1, response_body2, expected):
app = Flask(__name__)