Adding some niceties around the way users are associated with tokens. (#1012)
* Adding some niceties around the way users are associated with tokens. - Includes user typeahead - Tooltips - User information displayed in table - Default to current user when no user is passed
This commit is contained in:
parent
a756a74b49
commit
ad88637f22
|
@ -5,21 +5,27 @@
|
||||||
:license: Apache, see LICENSE for more details.
|
:license: Apache, see LICENSE for more details.
|
||||||
.. moduleauthor:: Eric Coan <kungfury@instructure.com>
|
.. moduleauthor:: Eric Coan <kungfury@instructure.com>
|
||||||
"""
|
"""
|
||||||
|
from flask import g
|
||||||
from marshmallow import fields
|
from marshmallow import fields
|
||||||
|
|
||||||
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
||||||
|
from lemur.users.schemas import UserNestedOutputSchema, UserInputSchema
|
||||||
|
|
||||||
|
|
||||||
|
def current_user_id():
|
||||||
|
return {'id': g.current_user.id, 'email': g.current_user.email, 'username': g.current_user.username}
|
||||||
|
|
||||||
|
|
||||||
class ApiKeyInputSchema(LemurInputSchema):
|
class ApiKeyInputSchema(LemurInputSchema):
|
||||||
name = fields.String(required=False)
|
name = fields.String(required=False)
|
||||||
user_id = fields.Integer()
|
user = fields.Nested(UserInputSchema, missing=current_user_id, default=current_user_id)
|
||||||
ttl = fields.Integer()
|
ttl = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
class ApiKeyRevokeSchema(LemurInputSchema):
|
class ApiKeyRevokeSchema(LemurInputSchema):
|
||||||
id = fields.Integer(required=False)
|
id = fields.Integer(required=True)
|
||||||
name = fields.String()
|
name = fields.String()
|
||||||
user_id = fields.Integer(required=False)
|
user = fields.Nested(UserInputSchema, required=True)
|
||||||
revoked = fields.Boolean()
|
revoked = fields.Boolean()
|
||||||
ttl = fields.Integer()
|
ttl = fields.Integer()
|
||||||
issued_at = fields.Integer(required=False)
|
issued_at = fields.Integer(required=False)
|
||||||
|
@ -37,7 +43,7 @@ class ApiKeyOutputSchema(LemurOutputSchema):
|
||||||
class ApiKeyDescribedOutputSchema(LemurOutputSchema):
|
class ApiKeyDescribedOutputSchema(LemurOutputSchema):
|
||||||
id = fields.Integer()
|
id = fields.Integer()
|
||||||
name = fields.String()
|
name = fields.String()
|
||||||
user_id = fields.Integer()
|
user = fields.Nested(UserNestedOutputSchema)
|
||||||
ttl = fields.Integer()
|
ttl = fields.Integer()
|
||||||
issued_at = fields.Integer()
|
issued_at = fields.Integer()
|
||||||
revoked = fields.Boolean()
|
revoked = fields.Boolean()
|
||||||
|
|
|
@ -28,6 +28,7 @@ api = Api(mod)
|
||||||
|
|
||||||
class ApiKeyList(AuthenticatedResource):
|
class ApiKeyList(AuthenticatedResource):
|
||||||
""" Defines the 'api_keys' endpoint """
|
""" Defines the 'api_keys' endpoint """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApiKeyList, self).__init__()
|
super(ApiKeyList, self).__init__()
|
||||||
|
|
||||||
|
@ -123,16 +124,17 @@ class ApiKeyList(AuthenticatedResource):
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
if not ApiKeyCreatorPermission().can():
|
if not ApiKeyCreatorPermission().can():
|
||||||
if data['user_id'] != g.current_user.id:
|
if data['user']['id'] != g.current_user.id:
|
||||||
return dict(message="You are not authorized to create tokens for: {0}".format(data['user_id'])), 403
|
return dict(message="You are not authorized to create tokens for: {0}".format(data['user']['username'])), 403
|
||||||
|
|
||||||
access_token = service.create(name=data['name'], user_id=data['user_id'], ttl=data['ttl'],
|
access_token = service.create(name=data['name'], user_id=data['user']['id'], ttl=data['ttl'],
|
||||||
revoked=False, issued_at=int(datetime.utcnow().timestamp()))
|
revoked=False, issued_at=int(datetime.utcnow().timestamp()))
|
||||||
return dict(jwt=create_token(access_token.user_id, access_token.id, access_token.ttl))
|
return dict(jwt=create_token(access_token.user_id, access_token.id, access_token.ttl))
|
||||||
|
|
||||||
|
|
||||||
class ApiKeyUserList(AuthenticatedResource):
|
class ApiKeyUserList(AuthenticatedResource):
|
||||||
""" Defines the 'keys' endpoint on the 'users' endpoint. """
|
""" Defines the 'keys' endpoint on the 'users' endpoint. """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApiKeyUserList, self).__init__()
|
super(ApiKeyUserList, self).__init__()
|
||||||
|
|
||||||
|
@ -231,7 +233,7 @@ class ApiKeyUserList(AuthenticatedResource):
|
||||||
return dict(message="You are not authorized to create tokens for: {0}".format(user_id)), 403
|
return dict(message="You are not authorized to create tokens for: {0}".format(user_id)), 403
|
||||||
|
|
||||||
access_token = service.create(name=data['name'], user_id=user_id, ttl=data['ttl'],
|
access_token = service.create(name=data['name'], user_id=user_id, ttl=data['ttl'],
|
||||||
revoked=False, issued_at=int(datetime.utcnow().timestamp()))
|
revoked=False, issued_at=int(datetime.utcnow().timestamp()))
|
||||||
return dict(jwt=create_token(access_token.user_id, access_token.id, access_token.ttl))
|
return dict(jwt=create_token(access_token.user_id, access_token.id, access_token.ttl))
|
||||||
|
|
||||||
|
|
||||||
|
@ -272,11 +274,14 @@ class ApiKeys(AuthenticatedResource):
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
access_key = service.get(aid)
|
access_key = service.get(aid)
|
||||||
|
|
||||||
if access_key is None:
|
if access_key is None:
|
||||||
return dict(message="This token does not exist!"), 404
|
return dict(message="This token does not exist!"), 404
|
||||||
|
|
||||||
if access_key.user_id != g.current_user.id:
|
if access_key.user_id != g.current_user.id:
|
||||||
if not ApiKeyCreatorPermission().can():
|
if not ApiKeyCreatorPermission().can():
|
||||||
return dict(message="You are not authorized to view this token!"), 403
|
return dict(message="You are not authorized to view this token!"), 403
|
||||||
|
|
||||||
return dict(jwt=create_token(access_key.user_id, access_key.id, access_key.ttl))
|
return dict(jwt=create_token(access_key.user_id, access_key.id, access_key.ttl))
|
||||||
|
|
||||||
@validate_schema(api_key_revoke_schema, api_key_output_schema)
|
@validate_schema(api_key_revoke_schema, api_key_output_schema)
|
||||||
|
@ -319,9 +324,11 @@ class ApiKeys(AuthenticatedResource):
|
||||||
access_key = service.get(aid)
|
access_key = service.get(aid)
|
||||||
if access_key is None:
|
if access_key is None:
|
||||||
return dict(message="This token does not exist!"), 404
|
return dict(message="This token does not exist!"), 404
|
||||||
|
|
||||||
if access_key.user_id != g.current_user.id:
|
if access_key.user_id != g.current_user.id:
|
||||||
if not ApiKeyCreatorPermission().can():
|
if not ApiKeyCreatorPermission().can():
|
||||||
return dict(message="You are not authorized to update this token!"), 403
|
return dict(message="You are not authorized to update this token!"), 403
|
||||||
|
|
||||||
service.update(access_key, name=data['name'], revoked=data['revoked'], ttl=data['ttl'])
|
service.update(access_key, name=data['name'], revoked=data['revoked'], ttl=data['ttl'])
|
||||||
return dict(jwt=create_token(access_key.user_id, access_key.id, access_key.ttl))
|
return dict(jwt=create_token(access_key.user_id, access_key.id, access_key.ttl))
|
||||||
|
|
||||||
|
@ -358,9 +365,11 @@ class ApiKeys(AuthenticatedResource):
|
||||||
access_key = service.get(aid)
|
access_key = service.get(aid)
|
||||||
if access_key is None:
|
if access_key is None:
|
||||||
return dict(message="This token does not exist!"), 404
|
return dict(message="This token does not exist!"), 404
|
||||||
|
|
||||||
if access_key.user_id != g.current_user.id:
|
if access_key.user_id != g.current_user.id:
|
||||||
if not ApiKeyCreatorPermission().can():
|
if not ApiKeyCreatorPermission().can():
|
||||||
return dict(message="You are not authorized to delete this token!"), 403
|
return dict(message="You are not authorized to delete this token!"), 403
|
||||||
|
|
||||||
service.delete(access_key)
|
service.delete(access_key)
|
||||||
return {'result': True}
|
return {'result': True}
|
||||||
|
|
||||||
|
@ -404,11 +413,15 @@ class UserApiKeys(AuthenticatedResource):
|
||||||
if uid != g.current_user.id:
|
if uid != g.current_user.id:
|
||||||
if not ApiKeyCreatorPermission().can():
|
if not ApiKeyCreatorPermission().can():
|
||||||
return dict(message="You are not authorized to view this token!"), 403
|
return dict(message="You are not authorized to view this token!"), 403
|
||||||
|
|
||||||
access_key = service.get(aid)
|
access_key = service.get(aid)
|
||||||
|
|
||||||
if access_key is None:
|
if access_key is None:
|
||||||
return dict(message="This token does not exist!"), 404
|
return dict(message="This token does not exist!"), 404
|
||||||
|
|
||||||
if access_key.user_id != uid:
|
if access_key.user_id != uid:
|
||||||
return dict(message="You are not authorized to view this token!"), 403
|
return dict(message="You are not authorized to view this token!"), 403
|
||||||
|
|
||||||
return dict(jwt=create_token(access_key.user_id, access_key.id, access_key.ttl))
|
return dict(jwt=create_token(access_key.user_id, access_key.id, access_key.ttl))
|
||||||
|
|
||||||
@validate_schema(api_key_revoke_schema, api_key_output_schema)
|
@validate_schema(api_key_revoke_schema, api_key_output_schema)
|
||||||
|
@ -451,11 +464,14 @@ class UserApiKeys(AuthenticatedResource):
|
||||||
if uid != g.current_user.id:
|
if uid != g.current_user.id:
|
||||||
if not ApiKeyCreatorPermission().can():
|
if not ApiKeyCreatorPermission().can():
|
||||||
return dict(message="You are not authorized to view this token!"), 403
|
return dict(message="You are not authorized to view this token!"), 403
|
||||||
|
|
||||||
access_key = service.get(aid)
|
access_key = service.get(aid)
|
||||||
if access_key is None:
|
if access_key is None:
|
||||||
return dict(message="This token does not exist!"), 404
|
return dict(message="This token does not exist!"), 404
|
||||||
|
|
||||||
if access_key.user_id != uid:
|
if access_key.user_id != uid:
|
||||||
return dict(message="You are not authorized to update this token!"), 403
|
return dict(message="You are not authorized to update this token!"), 403
|
||||||
|
|
||||||
service.update(access_key, name=data['name'], revoked=data['revoked'], ttl=data['ttl'])
|
service.update(access_key, name=data['name'], revoked=data['revoked'], ttl=data['ttl'])
|
||||||
return dict(jwt=create_token(access_key.user_id, access_key.id, access_key.ttl))
|
return dict(jwt=create_token(access_key.user_id, access_key.id, access_key.ttl))
|
||||||
|
|
||||||
|
@ -492,11 +508,14 @@ class UserApiKeys(AuthenticatedResource):
|
||||||
if uid != g.current_user.id:
|
if uid != g.current_user.id:
|
||||||
if not ApiKeyCreatorPermission().can():
|
if not ApiKeyCreatorPermission().can():
|
||||||
return dict(message="You are not authorized to view this token!"), 403
|
return dict(message="You are not authorized to view this token!"), 403
|
||||||
|
|
||||||
access_key = service.get(aid)
|
access_key = service.get(aid)
|
||||||
if access_key is None:
|
if access_key is None:
|
||||||
return dict(message="This token does not exist!"), 404
|
return dict(message="This token does not exist!"), 404
|
||||||
|
|
||||||
if access_key.user_id != uid:
|
if access_key.user_id != uid:
|
||||||
return dict(message="You are not authorized to delete this token!"), 403
|
return dict(message="You are not authorized to delete this token!"), 403
|
||||||
|
|
||||||
service.delete(access_key)
|
service.delete(access_key)
|
||||||
return {'result': True}
|
return {'result': True}
|
||||||
|
|
||||||
|
@ -545,9 +564,11 @@ class ApiKeysDescribed(AuthenticatedResource):
|
||||||
access_key = service.get(aid)
|
access_key = service.get(aid)
|
||||||
if access_key is None:
|
if access_key is None:
|
||||||
return dict(message="This token does not exist!"), 404
|
return dict(message="This token does not exist!"), 404
|
||||||
|
|
||||||
if access_key.user_id != g.current_user.id:
|
if access_key.user_id != g.current_user.id:
|
||||||
if not ApiKeyCreatorPermission().can():
|
if not ApiKeyCreatorPermission().can():
|
||||||
return dict(message="You are not authorized to view this token!"), 403
|
return dict(message="You are not authorized to view this token!"), 403
|
||||||
|
|
||||||
return access_key
|
return access_key
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,50 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('lemur')
|
angular.module('lemur')
|
||||||
.controller('ApiKeysCreateController', function ($scope, $uibModalInstance, PluginService, ApiKeyService, LemurRestangular, toaster) {
|
.controller('ApiKeysCreateController', function ($scope, $uibModalInstance, PluginService, ApiKeyService, UserService, LemurRestangular, toaster) {
|
||||||
$scope.apiKey = LemurRestangular.restangularizeElement(null, {}, 'keys');
|
$scope.apiKey = LemurRestangular.restangularizeElement(null, {}, 'keys');
|
||||||
|
|
||||||
|
$scope.origin = window.location.origin;
|
||||||
|
|
||||||
$scope.save = function (apiKey) {
|
$scope.save = function (apiKey) {
|
||||||
ApiKeyService.create(apiKey).then(
|
ApiKeyService.create(apiKey).then(
|
||||||
function (responseBody) {
|
function (responseBody) {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Success!',
|
title: 'Success!',
|
||||||
body: 'Successfully Created JWT!'
|
body: 'Successfully Created API Token!'
|
||||||
});
|
|
||||||
$scope.jwt = responseBody.jwt;
|
|
||||||
}, function (response) {
|
|
||||||
toaster.pop({
|
|
||||||
type: 'error',
|
|
||||||
title: apiKey.name || 'Unnamed Api Key',
|
|
||||||
body: 'lemur-bad-request',
|
|
||||||
bodyOutputType: 'directive',
|
|
||||||
directiveData: response.data,
|
|
||||||
timeout: 100000
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.cancel = function () {
|
|
||||||
$uibModalInstance.dismiss('cancel');
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.close = function() {
|
|
||||||
$uibModalInstance.close();
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.controller('ApiKeysEditController', function ($scope, $uibModalInstance, ApiKeyService, LemurRestangular, toaster, editId) {
|
|
||||||
LemurRestangular.one('keys', editId).customGET('described').then(function(apiKey) {
|
|
||||||
$scope.apiKey = apiKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.save = function (apiKey) {
|
|
||||||
ApiKeyService.update(apiKey).then(
|
|
||||||
function (responseBody) {
|
|
||||||
toaster.pop({
|
|
||||||
type: 'success',
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Successfully updated JWT!'
|
|
||||||
});
|
});
|
||||||
$scope.jwt = responseBody.jwt;
|
$scope.jwt = responseBody.jwt;
|
||||||
}, function (response) {
|
}, function (response) {
|
||||||
|
@ -66,4 +34,45 @@ angular.module('lemur')
|
||||||
$scope.close = function() {
|
$scope.close = function() {
|
||||||
$uibModalInstance.close();
|
$uibModalInstance.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.userService = UserService;
|
||||||
|
})
|
||||||
|
.controller('ApiKeysEditController', function ($scope, $uibModalInstance, ApiKeyService, UserService, LemurRestangular, toaster, editId) {
|
||||||
|
LemurRestangular.one('keys', editId).customGET('described').then(function(apiKey) {
|
||||||
|
$scope.apiKey = apiKey;
|
||||||
|
$scope.selectedUser = apiKey.user;
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.origin = window.location.origin;
|
||||||
|
|
||||||
|
$scope.save = function (apiKey) {
|
||||||
|
ApiKeyService.update(apiKey).then(
|
||||||
|
function (responseBody) {
|
||||||
|
toaster.pop({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Success',
|
||||||
|
body: 'Successfully updated API Token!'
|
||||||
|
});
|
||||||
|
$scope.jwt = responseBody.jwt;
|
||||||
|
}, function (response) {
|
||||||
|
toaster.pop({
|
||||||
|
type: 'error',
|
||||||
|
title: apiKey.name || 'Unnamed API Key',
|
||||||
|
body: 'lemur-bad-request',
|
||||||
|
bodyOutputType: 'directive',
|
||||||
|
directiveData: response.data,
|
||||||
|
timeout: 100000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.cancel = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function() {
|
||||||
|
$uibModalInstance.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.userService = UserService;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
<div class="modal-header" ng-show="!jwt">
|
<div class="modal-header" ng-show="!jwt">
|
||||||
<button type="button" class="close" ng-click="cancel()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
<button type="button" class="close" ng-click="cancel()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
<h3><span ng-show="!apiKey.fromServer">Create</span><span ng-show="apiKey.fromServer">Edit</span></h3>
|
<h3>
|
||||||
|
<span ng-show="!apiKey.fromServer">Create Token</span>
|
||||||
|
<span ng-show="apiKey.fromServer">Edit Token</span>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-header" ng-show="jwt">
|
<div class="modal-header" ng-show="jwt">
|
||||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
<h3><span>Token</span></h3>
|
<h3><span>Token Pickup</span></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" ng-show="!jwt">
|
<div class="modal-body" ng-show="!jwt">
|
||||||
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
||||||
|
@ -14,17 +17,20 @@
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input name="name" ng-model="apiKey.name" placeholder="A Cool API Key" class="form-control" required/>
|
<input name="name" ng-model="apiKey.name" placeholder="ExampleService" class="form-control" required/>
|
||||||
<p ng-show="createForm.label.$invalid && !createForm.label.$pristine" class="help-block">You must enter an api key name</p>
|
<p ng-show="createForm.label.$invalid && !createForm.label.$pristine" class="help-block">You must enter an api key name</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group"
|
<div class="form-group">
|
||||||
ng-class="{'has-error': createForm.user_id.$invalid, 'has-success': !createForm.user_id.$invalid&&createForm.user_id.$dirty}">
|
|
||||||
<label class="control-label col-sm-2">
|
<label class="control-label col-sm-2">
|
||||||
User ID
|
User
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input name="user_id" ng-model="apiKey.userId" placeholder="42" class="form-control" type="number" required/>
|
<input type="text" ng-model="apiKey.user" placeholder="My username..."
|
||||||
|
uib-typeahead="user.username for user in userService.findUserByName($viewValue)" typeahead-loading="loadingUsers"
|
||||||
|
class="form-control input-md" typeahead-on-select="apiKey.attachUser($item)"
|
||||||
|
uib-tooltip="This user will be tied to the generated key. All key permissions will mirror the users. Current user is the default."
|
||||||
|
uib-tooltip-trigger="focus" uib-tooltip-placement="top" typeahead-wait-ms="500">
|
||||||
<p ng-show="createForm.label.$invalid && !createForm.label.$pristine" class="help-block">You must enter an API Key User ID.</p>
|
<p ng-show="createForm.label.$invalid && !createForm.label.$pristine" class="help-block">You must enter an API Key User ID.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,15 +40,19 @@
|
||||||
TTL
|
TTL
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input name="ttl" ng-model="apiKey.ttl" placeholder="-1" class="form-control" type="number" required/>
|
<input name="ttl" ng-model="apiKey.ttl" placeholder="-1" class="form-control" type="number"
|
||||||
|
uib-tooltip="Number of days for the token to last. -1 meaning the token will not expire."
|
||||||
|
uib-tooltip-trigger="focus" uib-tooltip-placement="top" required/>
|
||||||
<p ng-show="createForm.label.$invalid && !createForm.label.$pristine" class="help-block">You must enter an API Key TTL.</p>
|
<p ng-show="createForm.label.$invalid && !createForm.label.$pristine" class="help-block">You must enter an API Key TTL.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="jwt">
|
<div class="modal-body" ng-show="jwt">
|
||||||
<h3>Successfully exported!</h3>
|
<h4>Pass the following token on every Lemur API request:</h4>
|
||||||
<h4>Your Token is: <pre><code>{{ jwt }}</code></pre></h4>
|
<pre><code>{{ jwt }}</code></pre>
|
||||||
|
<h4>Example usuage:</h4>
|
||||||
|
<pre><code>curl -i {{ origin }}/certificates -H "Authorization: Bearer {{ jwt }}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer" ng-show="!jwt">
|
<div class="modal-footer" ng-show="!jwt">
|
||||||
<button ng-click="save(apiKey)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save</button>
|
<button ng-click="save(apiKey)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save</button>
|
||||||
|
@ -51,4 +61,3 @@
|
||||||
<div class="modal-footer" ng-show="jwt">
|
<div class="modal-footer" ng-show="jwt">
|
||||||
<button ng-click="close()" class="btn btn-primary">Close</button>
|
<button ng-click="close()" class="btn btn-primary">Close</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,18 @@
|
||||||
|
|
||||||
angular.module('lemur')
|
angular.module('lemur')
|
||||||
.service('ApiKeyApi', function (LemurRestangular) {
|
.service('ApiKeyApi', function (LemurRestangular) {
|
||||||
|
LemurRestangular.extendModel('keys', function (obj) {
|
||||||
|
return angular.extend(obj, {
|
||||||
|
attachUser: function (user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
return LemurRestangular.all('keys');
|
return LemurRestangular.all('keys');
|
||||||
})
|
})
|
||||||
.service('ApiKeyService', function ($location, ApiKeyApi) {
|
.service('ApiKeyService', function ($location, ApiKeyApi) {
|
||||||
var ApiKeyService = this;
|
var ApiKeyService = this;
|
||||||
|
|
||||||
ApiKeyService.update = function(apiKey) {
|
ApiKeyService.update = function(apiKey) {
|
||||||
return apiKey.put();
|
return apiKey.put();
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
<td data-title="'Name'" sortable="'name'" align="center">
|
<td data-title="'Name'" sortable="'name'" align="center">
|
||||||
{{ apiKey.name || 'Unnamed Api Key' }}
|
{{ apiKey.name || 'Unnamed Api Key' }}
|
||||||
</td>
|
</td>
|
||||||
<td data-title="'User ID'" sortable="'user_id'" align="center">
|
<td data-title="'User'" align="center">
|
||||||
{{ apiKey.userId }}
|
{{ apiKey.user.email }}
|
||||||
</td>
|
</td>
|
||||||
<td data-title="'TTL'" align="center">
|
<td data-title="'TTL'" align="center">
|
||||||
{{ apiKey.ttl == -1 ? 'Forever' : apiKey.ttl }}
|
{{ apiKey.ttl == -1 ? 'Forever' : apiKey.ttl }}
|
||||||
|
|
|
@ -34,7 +34,7 @@ def test_api_key_list_post_invalid(client, token, status):
|
||||||
('', 0, 401)
|
('', 0, 401)
|
||||||
])
|
])
|
||||||
def test_api_key_list_post_valid_self(client, user_id, token, status):
|
def test_api_key_list_post_valid_self(client, user_id, token, status):
|
||||||
assert client.post(api.url_for(ApiKeyList), data=json.dumps({'name': 'a test token', 'userId': user_id, 'ttl': -1}), headers=token).status_code == status
|
assert client.post(api.url_for(ApiKeyList), data=json.dumps({'name': 'a test token', 'user': {'id': user_id, 'username': 'example', 'email': 'example@test.net'}, 'ttl': -1}), headers=token).status_code == status
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("token,status", [
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
@ -44,7 +44,7 @@ def test_api_key_list_post_valid_self(client, user_id, token, status):
|
||||||
('', 401)
|
('', 401)
|
||||||
])
|
])
|
||||||
def test_api_key_list_post_valid_no_permission(client, token, status):
|
def test_api_key_list_post_valid_no_permission(client, token, status):
|
||||||
assert client.post(api.url_for(ApiKeyList), data=json.dumps({'name': 'a test token', 'userId': 2, 'ttl': -1}), headers=token).status_code == status
|
assert client.post(api.url_for(ApiKeyList), data=json.dumps({'name': 'a test token', 'user': {'id': 2, 'username': 'example', 'email': 'example@test.net'}, 'ttl': -1}), headers=token).status_code == status
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("token,status", [
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
@ -94,7 +94,7 @@ def test_user_api_key_list_post_invalid(client, token, status):
|
||||||
('', 0, 401)
|
('', 0, 401)
|
||||||
])
|
])
|
||||||
def test_user_api_key_list_post_valid_self(client, user_id, token, status):
|
def test_user_api_key_list_post_valid_self(client, user_id, token, status):
|
||||||
assert client.post(api.url_for(ApiKeyUserList, user_id=1), data=json.dumps({'name': 'a test token', 'userId': user_id, 'ttl': -1}), headers=token).status_code == status
|
assert client.post(api.url_for(ApiKeyUserList, user_id=1), data=json.dumps({'name': 'a test token', 'user': {'id': user_id}, 'ttl': -1}), headers=token).status_code == status
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("token,status", [
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
@ -104,7 +104,7 @@ def test_user_api_key_list_post_valid_self(client, user_id, token, status):
|
||||||
('', 401)
|
('', 401)
|
||||||
])
|
])
|
||||||
def test_user_api_key_list_post_valid_no_permission(client, token, status):
|
def test_user_api_key_list_post_valid_no_permission(client, token, status):
|
||||||
assert client.post(api.url_for(ApiKeyUserList, user_id=2), data=json.dumps({'name': 'a test token', 'userId': 2, 'ttl': -1}), headers=token).status_code == status
|
assert client.post(api.url_for(ApiKeyUserList, user_id=2), data=json.dumps({'name': 'a test token', 'user': {'id': 2}, 'ttl': -1}), headers=token).status_code == status
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("token,status", [
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
|
|
@ -44,6 +44,7 @@ class User(db.Model):
|
||||||
roles = relationship('Role', secondary=roles_users, passive_deletes=True, backref=db.backref('user'), lazy='dynamic')
|
roles = relationship('Role', secondary=roles_users, passive_deletes=True, backref=db.backref('user'), lazy='dynamic')
|
||||||
certificates = relationship('Certificate', backref=db.backref('user'), lazy='dynamic')
|
certificates = relationship('Certificate', backref=db.backref('user'), lazy='dynamic')
|
||||||
authorities = relationship('Authority', backref=db.backref('user'), lazy='dynamic')
|
authorities = relationship('Authority', backref=db.backref('user'), lazy='dynamic')
|
||||||
|
keys = relationship('ApiKey', backref=db.backref('user'), lazy='dynamic')
|
||||||
logs = relationship('Log', backref=db.backref('user'), lazy='dynamic')
|
logs = relationship('Log', backref=db.backref('user'), lazy='dynamic')
|
||||||
|
|
||||||
sensitive_fields = ('password',)
|
sensitive_fields = ('password',)
|
||||||
|
|
|
@ -12,6 +12,7 @@ from lemur.schemas import AssociatedRoleSchema, AssociatedCertificateSchema, Ass
|
||||||
|
|
||||||
|
|
||||||
class UserInputSchema(LemurInputSchema):
|
class UserInputSchema(LemurInputSchema):
|
||||||
|
id = fields.Integer()
|
||||||
username = fields.String(required=True)
|
username = fields.String(required=True)
|
||||||
email = fields.Email(required=True)
|
email = fields.Email(required=True)
|
||||||
password = fields.String() # TODO add complexity requirements
|
password = fields.String() # TODO add complexity requirements
|
||||||
|
|
Loading…
Reference in New Issue