Add an AppSpecificAuthToken data model for app-specific auth tokens. These will be used for the Docker CLI in place of username+password

This commit is contained in:
Joseph Schorr 2017-12-08 17:05:59 -05:00
parent 53b762a875
commit 524d77f527
50 changed files with 943 additions and 289 deletions

View file

@ -0,0 +1,33 @@
<div class="resource-view" resource="$ctrl.appTokensResource">
<div style="float: right; margin-left: 10px;">
<button class="btn btn-primary" ng-click="$ctrl.askCreateToken()">Create Application Token</button>
</div>
<cor-table table-data="$ctrl.appTokens" table-item-title="tokens" filter-fields="['title']">
<cor-table-col datafield="title" sortfield="title" title="Title" selected="true"
bind-model="$ctrl"
templateurl="/static/js/directives/ui/app-specific-token-manager/token-title.html"></cor-table-col>
<cor-table-col datafield="last_accessed" sortfield="last_accessed" title="Last Accessed"
kindof="datetime" templateurl="/static/js/directives/ui/app-specific-token-manager/last-accessed.html"></cor-table-col>
<cor-table-col datafield="expiration" sortfield="expiration" title="Expiration"
kindof="datetime" templateurl="/static/js/directives/ui/app-specific-token-manager/expiration.html"></cor-table-col>
<cor-table-col datafield="created" sortfield="created" title="Created"
kindof="datetime" templateurl="/static/js/directives/ui/app-specific-token-manager/created.html"></cor-table-col>
<cor-table-col templateurl="/static/js/directives/ui/app-specific-token-manager/cog.html"
bind-model="$ctrl" class="options-col"></cor-table-col>
</cor-table>
<div class="credentials-dialog" credentials="$ctrl.tokenCredentials" secret-title="Application Token" entity-title="application token" entity-icon="fa-key"></div>
<!-- Revoke token confirm -->
<div class="cor-confirm-dialog"
dialog-context="$ctrl.revokeTokenInfo"
dialog-action="$ctrl.revokeToken(info.token, callback)"
dialog-title="Revoke Application Token"
dialog-action-title="Revoke Token">
<div class="co-alert co-alert-warning" style="margin-bottom: 10px;">
Application token "{{ $ctrl.revokeTokenInfo.token.title }}" will be revoked and <strong>all</strong> applications and CLIs making use of the token will no longer operate.
</div>
Proceed with revocation of this token?
</div>
</div>

View file

@ -0,0 +1,71 @@
import { Input, Component, Inject } from 'ng-metadata/core';
import * as bootbox from "bootbox";
/**
* A component that displays and manage all app specific tokens for a user.
*/
@Component({
selector: 'app-specific-token-manager',
templateUrl: '/static/js/directives/ui/app-specific-token-manager/app-specific-token-manager.component.html',
})
export class AppSpecificTokenManagerComponent {
private appTokensResource: any;
private appTokens: Array<any>;
private tokenCredentials: any;
private revokeTokenInfo: any;
constructor(@Inject('ApiService') private ApiService: any, @Inject('UserService') private UserService: any) {
this.loadTokens();
}
private loadTokens() {
this.appTokensResource = this.ApiService.listAppTokensAsResource().get((resp) => {
this.appTokens = resp['tokens'];
});
}
private askCreateToken() {
bootbox.prompt('Please enter a descriptive title for the new application token', (title) => {
if (!title) { return; }
const errorHandler = this.ApiService.errorDisplay('Could not create the application token');
this.ApiService.createAppToken({title}).then((resp) => {
this.loadTokens();
}, errorHandler);
});
}
private showRevokeToken(token) {
this.revokeTokenInfo = {
'token': token,
};
};
private revokeToken(token, callback) {
const errorHandler = this.ApiService.errorDisplay('Could not revoke application token', callback);
const params = {
'token_uuid': token['uuid'],
};
this.ApiService.revokeAppToken(null, params).then((resp) => {
this.loadTokens();
callback(true);
}, errorHandler);
}
private showToken(token) {
const errorHandler = this.ApiService.errorDisplay('Could not find application token');
const params = {
'token_uuid': token['uuid'],
};
this.ApiService.getAppToken(null, params).then((resp) => {
this.tokenCredentials = {
'title': resp['token']['title'],
'namespace': this.UserService.currentUser().username,
'username': '$app',
'password': resp['token']['token_code'],
};
}, errorHandler);
}
}

View file

@ -0,0 +1,5 @@
<span class="cor-options-menu">
<span class="cor-option" option-click="col.bindModel.showRevokeToken(item)">
<i class="fa fa-times"></i> Revoke Token
</span>
</span>

View file

@ -0,0 +1 @@
<time-ago datetime="item.created"></time-ago>

View file

@ -0,0 +1 @@
<expiration-status-view expiration-date="item.expiration"></expiration-status-view>

View file

@ -0,0 +1 @@
<time-ago datetime="item.last_accessed"></time-ago>

View file

@ -0,0 +1 @@
<a ng-click="col.bindModel.showToken(item)">{{ item.title }}</a>

View file

@ -36,7 +36,7 @@ export class CorTableColumn implements OnInit {
}
public processColumnForOrdered(value: any): any {
if (this.kindof == 'datetime') {
if (this.kindof == 'datetime' && value) {
return this.tableService.getReversedTimestamp(value);
}

View file

@ -41,7 +41,8 @@
<table class="co-table co-fixed-table" ng-show="$ctrl.tableData.length">
<thead>
<td ng-repeat="col in $ctrl.columns"
ng-class="$ctrl.tablePredicateClass(col)" style="{{ ::col.style }}">
ng-class="$ctrl.tablePredicateClass(col)" style="{{ ::col.style }}"
class="{{ ::col.class }}">
<a ng-click="$ctrl.setOrder(col)">{{ ::col.title }}</a>
</td>
</thead>

View file

@ -170,7 +170,7 @@ angular.module('quay').directive('credentialsDialog', function () {
return '';
}
return $scope.getEscapedUsername(credentials).toLowerCase() + '-pull-secret';
return $scope.getSuffixedFilename(credentials, 'pull-secret');
};
$scope.getKubernetesFile = function(credentials) {
@ -193,8 +193,12 @@ angular.module('quay').directive('credentialsDialog', function () {
return $scope.getSuffixedFilename(credentials, 'secret.yml')
};
$scope.getEscapedUsername = function(credentials) {
return credentials.username.replace(/[^a-zA-Z0-9]/g, '-');
$scope.getEscaped = function(item) {
var escaped = item.replace(/[^a-zA-Z0-9]/g, '-');
if (escaped[0] == '-') {
escaped = escaped.substr(1);
}
return escaped;
};
$scope.getSuffixedFilename = function(credentials, suffix) {
@ -202,7 +206,12 @@ angular.module('quay').directive('credentialsDialog', function () {
return '';
}
return $scope.getEscapedUsername(credentials) + '-' + suffix;
var prefix = $scope.getEscaped(credentials.username);
if (credentials.title) {
prefix = $scope.getEscaped(credentials.title);
}
return prefix + '-' + suffix;
};
}
};

View file

@ -283,6 +283,9 @@ angular.module('quay').directive('logsView', function () {
}
},
'create_app_specific_token': 'Created external application token {app_specific_token_title}',
'revoke_app_specific_token': 'Revoked external application token {app_specific_token_title}',
// Note: These are deprecated.
'add_repo_webhook': 'Add webhook in repository {repo}',
'delete_repo_webhook': 'Delete webhook in repository {repo}'
@ -345,7 +348,9 @@ angular.module('quay').directive('logsView', function () {
'manifest_label_add': 'Add Manifest Label',
'manifest_label_delete': 'Delete Manifest Label',
'change_tag_expiration': 'Change tag expiration',
'create_app_specific_token': 'Create external app token',
'revoke_app_specific_token': 'Revoke external app token',
// Note: these are deprecated.
'add_repo_webhook': 'Add webhook',
'delete_repo_webhook': 'Delete webhook'

View file

@ -2,5 +2,5 @@
<span ng-if="$ctrl.datetime" data-title="{{ $ctrl.datetime | amDateFormat:'llll' }}" bs-tooltip>
<span am-time-ago="$ctrl.datetime"></span>
</span>
<span ng-if="!$ctrl.datetime">Unknown</span>
<span ng-if="!$ctrl.datetime">Never</span>
</span>