From dec14647a85614335e7e5119b4b6c7acbbdc3357 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 17 Apr 2017 18:03:57 -0400 Subject: [PATCH] Add basic signing UI to repo tags view --- static/css/core-ui.css | 4 + .../directives/repo-view/repo-panel-tags.css | 4 + .../css/directives/ui/tag-signing-display.css | 55 +++++++++++ .../directives/repo-view/repo-panel-tags.html | 11 ++- .../directives/repo-view/repo-panel-tags.js | 20 +++- .../tag-signing-display.component.html | 49 ++++++++++ .../tag-signing-display.component.ts | 98 +++++++++++++++++++ static/js/quay.module.ts | 2 + 8 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 static/css/directives/ui/tag-signing-display.css create mode 100644 static/js/directives/ui/tag-signing-display/tag-signing-display.component.html create mode 100644 static/js/directives/ui/tag-signing-display/tag-signing-display.component.ts 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 75d65b75e..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; } 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/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html index 46d248fa9..154dcfd97 100644 --- a/static/directives/repo-view/repo-panel-tags.html +++ b/static/directives/repo-view/repo-panel-tags.html @@ -85,13 +85,17 @@ Tag + Sign + Last Modified Security Scan @@ -124,6 +128,11 @@ + + + diff --git a/static/js/directives/repo-view/repo-panel-tags.js b/static/js/directives/repo-view/repo-panel-tags.js index 159a61d9a..7bb43555e 100644 --- a/static/js/directives/repo-view/repo-panel-tags.js +++ b/static/js/directives/repo-view/repo-panel-tags.js @@ -39,9 +39,26 @@ angular.module('quay').directive('repoPanelTags', function () { $scope.labelCache = {}; $scope.imageVulnerabilities = {}; + $scope.repoSignatureInfo = null; + $scope.defcon1 = {}; $scope.hasDefcon1 = false; + var loadRepoSignatures = function() { + $scope.repoSignatureError = false; + $scope.repoSignatureInfo = null; + + var params = { + 'repository': $scope.repository.namespace + '/' + $scope.repository.name + }; + + ApiService.getRepoSignatures(null, params).then(function(resp) { + $scope.repoSignatureInfo = resp; + }, function() { + $scope.repoSignatureInfo = {'error': true}; + }); + }; + var setTagState = function() { if (!$scope.repository || !$scope.selectedTags) { return; } @@ -190,6 +207,7 @@ angular.module('quay').directive('repoPanelTags', function () { // Process each of the tags. setTagState(); + loadRepoSignatures(); }); $scope.loadImageVulnerabilities = function(image_id, imageData) { @@ -244,7 +262,7 @@ angular.module('quay').directive('repoPanelTags', function () { $scope.getImageVulnerabilities = function(image_id) { if (!$scope.repository) { - return + return; } if (!$scope.imageVulnerabilities[image_id]) { diff --git a/static/js/directives/ui/tag-signing-display/tag-signing-display.component.html b/static/js/directives/ui/tag-signing-display/tag-signing-display.component.html new file mode 100644 index 000000000..be57ff502 --- /dev/null +++ b/static/js/directives/ui/tag-signing-display/tag-signing-display.component.html @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/js/directives/ui/tag-signing-display/tag-signing-display.component.ts b/static/js/directives/ui/tag-signing-display/tag-signing-display.component.ts new file mode 100644 index 000000000..75c068fc2 --- /dev/null +++ b/static/js/directives/ui/tag-signing-display/tag-signing-display.component.ts @@ -0,0 +1,98 @@ +import { Input, Component, Inject } from 'ng-metadata/core'; +import * as moment from "moment"; + +interface ApostilleSignatureDocument { + // When the signed document expires. + expiration: string + + // Object of information for each tag. + tags: {string: ApostilleTagDocument} + + // If true, an error occurred while trying to load this document. + error: boolean +} + +interface ApostilleTagDocument { + // The length of the document. + length: number + + // The hashes for the tag. + hashes: {string: string} +} + +/** + * A component that displays the signing status of a tag in the repository view. + */ +@Component({ + selector: 'tag-signing-display', + templateUrl: '/static/js/directives/ui/tag-signing-display/tag-signing-display.component.html', +}) +export class TagSigningDisplayComponent { + @Input('<') public tag: any; + @Input('=') public signatures: ApostilleSignatureDocument; + + private signedDigest: string; + private pushedDigest: string; + + 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); + var hexString = ''; + for (var i = 0; i < raw.length; ++i) { + var char = raw.charCodeAt(i); + var hex = char.toString(16) + hexString += (hex.length == 2 ? hex : '0' + hex); + } + return hexString; + } + + private expirationStatus(tag: any, signatures: ApostilleSignatureDocument): string { + if (!signatures || !signatures.expiration) { + return 'unknown'; + } + + var expires = moment(signatures.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'; + } + + private signingStatus(tag: any, signatures: ApostilleSignatureDocument): string { + if (!tag || !signatures) { + return 'loading'; + } + + if (signatures.error || !signatures.tags) { + return 'error'; + } + + var tag_info = signatures.tags[tag.name]; + if (!tag_info || !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'; + } + } +} \ No newline at end of file diff --git a/static/js/quay.module.ts b/static/js/quay.module.ts index 91139a7a4..8538957d5 100644 --- a/static/js/quay.module.ts +++ b/static/js/quay.module.ts @@ -14,6 +14,7 @@ import { VisibilityIndicatorComponent } from './directives/ui/visibility-indicat import { CorTableComponent } from './directives/ui/cor-table/cor-table.component'; import { CorTableColumn } from './directives/ui/cor-table/cor-table-col.component'; import { ChannelIconComponent } from './directives/ui/channel-icon/channel-icon.component'; +import { TagSigningDisplayComponent } from './directives/ui/tag-signing-display/tag-signing-display.component'; import { BuildServiceImpl } from './services/build/build.service.impl'; import { AvatarServiceImpl } from './services/avatar/avatar.service.impl'; import { DockerfileServiceImpl } from './services/dockerfile/dockerfile.service.impl'; @@ -44,6 +45,7 @@ import { QuayRequireDirective } from './directives/structural/quay-require/quay- CorTableColumn, ChannelIconComponent, QuayRequireDirective, + TagSigningDisplayComponent, ], providers: [ ViewArrayImpl,