Users may now issue their own access tokens.

A user may now issue their own access tokens, as well as deactivate
existing access tokens, using the web UI. It may be used for direct API
access using the python SDK.

Change-Id: I01a59c4fad6459299ee846879065b256326efedb
This commit is contained in:
Michael Krotscheck 2014-12-08 14:54:17 -08:00
parent 40d06c9cf0
commit 4b02ea285f
9 changed files with 385 additions and 4 deletions

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Issue token controller.
*/
angular.module('sb.profile').controller('ProfileTokenNewController',
function ($q, $log, $scope, $modalInstance, UserToken, user) {
'use strict';
/**
* Flag for the UI to indicate that we're saving.
*
* @type {boolean}
*/
$scope.isSaving = false;
/**
* The new token.
*
* @type {UserToken}
*/
$scope.token = new UserToken({
user_id: user.id,
expires_in: 3600
});
/**
* Saves the project group
*/
$scope.save = function () {
$scope.isSaving = true;
$scope.token.$create(
function (token) {
$modalInstance.close(token);
$scope.isSaving = false;
},
function () {
$scope.isSaving = false;
}
);
};
/**
* Close this modal without saving.
*/
$scope.close = function () {
$modalInstance.dismiss('cancel');
};
});

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* User profile controller for all of a user's auth tokens.
*/
angular.module('sb.profile').controller('ProfileTokensController',
function ($scope, UserToken, tokens, $modal) {
'use strict';
$scope.tokens = tokens;
$scope.deleteToken = function (token) {
token.$delete(function () {
var idx = $scope.tokens.indexOf(token);
if (idx > -1) {
$scope.tokens.splice(idx, 1);
}
});
};
$scope.issueToken = function () {
$modal.open({
templateUrl: 'app/profile/template/token_new.html',
controller: 'ProfileTokenNewController',
resolve: {
user: function (CurrentUser) {
return CurrentUser.resolve();
}
}
}).result.then(function (token) {
// On success, append the token.
$scope.tokens.push(token);
});
};
});
/**
* Controller for a single token row within the profile token view.
*/
angular.module('sb.profile').controller('ProfileTokenItemController',
function ($scope, AccessToken) {
'use strict';
var now = new Date();
// Render the expiration date.
$scope.created = new Date($scope.token.created_at);
$scope.expires = new Date($scope.token.created_at);
$scope.expires.setSeconds($scope.expires.getSeconds() +
$scope.token.expires_in);
$scope.expired = $scope.expires.getTime() < now.getTime();
$scope.current =
$scope.token.access_token === AccessToken.getAccessToken();
});

View File

@ -23,8 +23,8 @@
* @author Michael Krotscheck
*/
angular.module('sb.profile',
['sb.services', 'sb.templates', 'sb.auth', 'ui.router', 'ui.bootstrap']
)
['sb.services', 'sb.templates', 'sb.auth', 'ui.router', 'ui.bootstrap']
)
.config(function ($stateProvider, SessionResolver, $urlRouterProvider) {
'use strict';
@ -40,7 +40,7 @@ angular.module('sb.profile',
isLoggedIn: SessionResolver.requireLoggedIn,
currentUser: SessionResolver.requireCurrentUser
},
views : {
views: {
'submenu@': {
templateUrl: 'app/profile/template/profile_submenu.html'
},
@ -53,5 +53,32 @@ angular.module('sb.profile',
url: '/preferences',
templateUrl: 'app/profile/template/preferences.html',
controller: 'ProfilePreferencesController'
})
.state('profile.tokens', {
url: '/tokens',
templateUrl: 'app/profile/template/tokens.html',
controller: 'ProfileTokensController',
resolve: {
tokens: function (CurrentUser, UserToken, $q) {
var deferred = $q.defer();
CurrentUser.resolve().then(
function (currentUser) {
UserToken.query({
user_id: currentUser.id
}, function (results) {
deferred.resolve(results);
}, function (error) {
deferred.reject(error);
}
);
},
function (error) {
deferred.reject(error);
}
);
return deferred.promise;
}
}
});
});

View File

@ -17,7 +17,7 @@
<div class="container" ng-hide="isLoading">
<div class="row">
<div class="col-xs-12">
<h1>Preferences</h1>
<h1><i class="fa fa-sb-profile-preferences"></i> Preferences</h1>
</div>
</div>
<div class="row">

View File

@ -20,4 +20,9 @@
<i class="fa fa-sb-profile-preferences fa-lg"></i>
</a>
</li>
<li active-path="^\/profile/tokens.*">
<a href="#!/profile/tokens" title="Authentication Tokens">
<i class="fa fa-sb-profile-tokens fa-lg"></i>
</a>
</li>
</ul>

View File

@ -0,0 +1,102 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ Licensed under the Apache License, Version 2.0 (the "License"); you may
~ not use this file except in compliance with the License. You may obtain
~ a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<div class="panel panel-default">
<div class="panel-heading">
<button type="button" class="close"
ng-click="close()">&times;</button>
<h3 class="panel-title">Issue Access Token</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-xs-12">
<form class="form-horizontal" role="form" name="tokenForm">
<div class="form-group">
<label for="expires_in" class="col-sm-3 control-label">
Expires in:
</label>
<div class="col-sm-9">
<select id="expires_in" class="form-control"
ng-model="token.expires_in"
required
ng-disabled="isSaving">
<option value="3600">1 hour</option>
<option value="86400">1 day</option>
<option value="604800">1 week</option>
<option value="31536000">1 year</option>
<option value="315360000">1 decade</option>
</select>
</div>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-xs-6">
</div>
<div class="col-xs-6 text-right">
<button type="button"
class="btn btn-primary"
ng-click="save()"
ng-disabled="!tokenForm.$valid || isSaving">
Issue Token
</button>
<button type="button"
ng-click="close()"
ng-disabled="isSaving"
class="btn btn-default">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Template for story metadata -->
<script type="text/ng-template" id="/inline/project_row.html">
<td class="col-xs-11">
<div class="has-feedback has-feedback-no-label">
<input id="project"
type="text"
placeholder="Select a Project"
required
ng-model="project"
typeahead-editable="false"
typeahead-wait-ms="200"
typeahead="project as project.name for project
in searchProjects($viewValue)"
typeahead-loading="loadingProjects"
typeahead-on-select="selectNewProject(index, $model)"
typeahead-input-formatter="formatProjectName($model)"
ng-disabled="isSaving"
class="form-control input-sm"
/>
<span class="form-control-feedback text-muted
form-control-feedback-sm">
<i class="fa fa-refresh fa-spin" ng-show="loadingProjects"></i>
<i class="fa fa-search" ng-hide="loadingProjects"></i>
</span>
</div>
</td>
<th class="col-xs-1"
ng-show="projects.length > 1">
<button type="button" class="close"
ng-click="removeProject(index)">
&times;
</button>
</th>
</script>

View File

@ -0,0 +1,78 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ Licensed under the Apache License, Version 2.0 (the "License"); you may
~ not use this file except in compliance with the License. You may obtain
~ a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<div class="container" ng-hide="isLoading">
<div class="row">
<div class="col-xs-12">
<h1><i class="fa fa-sb-profile-tokens"></i> Authentication
Tokens</h1>
</div>
<div class="col-xs-9 col-sm-10">
<p class="lead">Authorize and deauthorize authentication tokens
for StoryBoard.</p>
</div>
<div class="col-xs-3 col-sm-2">
<button type="button"
class="btn btn-primary btn-sm pull-right"
ng-click="issueToken()">
<i class="fa fa-plus"></i>
<span class="hidden-sm hidden-xs">Issue Token</span>
</button>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<table class="table table-condensed">
<thead>
<tr>
<th>Token</th>
<th>Issued</th>
<th>Expires</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="token in tokens"
ng-controller="ProfileTokenItemController">
<td>
<span class="label label-success"
ng-if="current">
Current
</span>
<span class="label label-danger"
ng-if="expired && !current">
Expired
</span>
<span class="label label-info"
ng-if="!expired && !current">
Active
</span>
&nbsp;{{token.access_token}}
</td>
<td><span time-moment eventdate="created"></span></td>
<td><span time-moment eventdate="expires"></span></td>
<td>
<button type="button"
class="close"
ng-click="deleteToken(token)"
ng-if="!current">&times;</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* The angular resource abstraction that allows us to search, access, and
* modify individual user's authentication tokens.
*
* @see ResourceFactory
* @author Michael Krotscheck
*/
angular.module('sb.services').factory('UserToken',
function (ResourceFactory) {
'use strict';
var resource = ResourceFactory.build(
'/users/:user_id/tokens/:id',
null,
{user_id: '@user_id', id: '@id'}
);
return resource;
});

View File

@ -29,3 +29,4 @@
.@{fa-css-prefix}-sb-admin:before { content: @fa-var-gears; }
.@{fa-css-prefix}-sb-profile:before { content: @fa-var-user; }
.@{fa-css-prefix}-sb-profile-preferences:before { content: @fa-var-gear; }
.@{fa-css-prefix}-sb-profile-tokens:before { content: @fa-var-key; }