Making roles more apparent for certificates and authorities. (#327)
This commit is contained in:
parent
e04c1e7dc9
commit
bd727b825d
|
@ -18,6 +18,9 @@ admin_permission = Permission(RoleNeed('admin'))
|
||||||
CertificateCreator = namedtuple('certificate', ['method', 'value'])
|
CertificateCreator = namedtuple('certificate', ['method', 'value'])
|
||||||
CertificateCreatorNeed = partial(CertificateCreator, 'key')
|
CertificateCreatorNeed = partial(CertificateCreator, 'key')
|
||||||
|
|
||||||
|
CertificateOwner = namedtuple('certificate', ['method', 'value'])
|
||||||
|
CertificateOwnerNeed = partial(CertificateOwner, 'role')
|
||||||
|
|
||||||
|
|
||||||
class SensitiveDomainPermission(Permission):
|
class SensitiveDomainPermission(Permission):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -36,6 +39,15 @@ class UpdateCertificatePermission(Permission):
|
||||||
super(UpdateCertificatePermission, self).__init__(c_need, RoleNeed(owner), RoleNeed('admin'))
|
super(UpdateCertificatePermission, self).__init__(c_need, RoleNeed(owner), RoleNeed('admin'))
|
||||||
|
|
||||||
|
|
||||||
|
class CertificatePermission(Permission):
|
||||||
|
def __init__(self, certificate_id, roles):
|
||||||
|
needs = [RoleNeed('admin'), CertificateCreatorNeed(certificate_id)]
|
||||||
|
for r in roles:
|
||||||
|
needs.append(CertificateOwnerNeed(str(r)))
|
||||||
|
|
||||||
|
super(CertificatePermission, self).__init__(*needs)
|
||||||
|
|
||||||
|
|
||||||
RoleUser = namedtuple('role', ['method', 'value'])
|
RoleUser = namedtuple('role', ['method', 'value'])
|
||||||
ViewRoleCredentialsNeed = partial(RoleUser, 'roleView')
|
ViewRoleCredentialsNeed = partial(RoleUser, 'roleView')
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,7 @@ def on_identity_loaded(sender, identity):
|
||||||
# identity with the roles that the user provides
|
# identity with the roles that the user provides
|
||||||
if hasattr(user, 'roles'):
|
if hasattr(user, 'roles'):
|
||||||
for role in user.roles:
|
for role in user.roles:
|
||||||
identity.provides.add(ViewRoleCredentialsNeed(role.id))
|
identity.provides.add(ViewRoleCredentialsNeed(role.name))
|
||||||
identity.provides.add(RoleNeed(role.name))
|
identity.provides.add(RoleNeed(role.name))
|
||||||
|
|
||||||
# apply ownership for authorities
|
# apply ownership for authorities
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
:license: Apache, see LICENSE for more details.
|
:license: Apache, see LICENSE for more details.
|
||||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
"""
|
"""
|
||||||
from flask import Blueprint, g
|
from flask import Blueprint
|
||||||
from flask.ext.restful import reqparse, Api
|
from flask.ext.restful import reqparse, Api
|
||||||
|
|
||||||
from lemur.common.utils import paginated_parser
|
from lemur.common.utils import paginated_parser
|
||||||
|
@ -13,7 +13,6 @@ from lemur.common.schema import validate_schema
|
||||||
from lemur.auth.service import AuthenticatedResource
|
from lemur.auth.service import AuthenticatedResource
|
||||||
from lemur.auth.permissions import AuthorityPermission
|
from lemur.auth.permissions import AuthorityPermission
|
||||||
|
|
||||||
from lemur.roles import service as role_service
|
|
||||||
from lemur.certificates import service as certificate_service
|
from lemur.certificates import service as certificate_service
|
||||||
|
|
||||||
from lemur.authorities import service
|
from lemur.authorities import service
|
||||||
|
@ -270,24 +269,11 @@ class Authorities(AuthenticatedResource):
|
||||||
if not authority:
|
if not authority:
|
||||||
return dict(message='Not Found'), 404
|
return dict(message='Not Found'), 404
|
||||||
|
|
||||||
role = role_service.get_by_name(authority.owner)
|
|
||||||
|
|
||||||
# all the authority role members should be allowed
|
# all the authority role members should be allowed
|
||||||
roles = [x.name for x in authority.roles]
|
roles = [x.name for x in authority.roles]
|
||||||
|
|
||||||
# allow "owner" roles by team DL
|
|
||||||
roles.append(role)
|
|
||||||
permission = AuthorityPermission(authority_id, roles)
|
permission = AuthorityPermission(authority_id, roles)
|
||||||
|
|
||||||
if permission.can():
|
if permission.can():
|
||||||
# we want to make sure that we cannot add roles that we are not members of
|
|
||||||
if not g.current_user.is_admin:
|
|
||||||
role_ids = set([r.id for r in data['roles']])
|
|
||||||
user_role_ids = set([r.id for r in g.current_user.roles])
|
|
||||||
|
|
||||||
if not role_ids.issubset(user_role_ids):
|
|
||||||
return dict(message="You are not allowed to associate a role which you are not a member of."), 403
|
|
||||||
|
|
||||||
return service.update(
|
return service.update(
|
||||||
authority_id,
|
authority_id,
|
||||||
owner=data['owner'],
|
owner=data['owner'],
|
||||||
|
|
|
@ -11,11 +11,12 @@ from marshmallow import fields, validates_schema, post_load
|
||||||
from marshmallow.exceptions import ValidationError
|
from marshmallow.exceptions import ValidationError
|
||||||
|
|
||||||
from lemur.schemas import AssociatedAuthoritySchema, AssociatedDestinationSchema, AssociatedCertificateSchema, \
|
from lemur.schemas import AssociatedAuthoritySchema, AssociatedDestinationSchema, AssociatedCertificateSchema, \
|
||||||
AssociatedNotificationSchema, PluginInputSchema, ExtensionSchema
|
AssociatedNotificationSchema, PluginInputSchema, ExtensionSchema, AssociatedRoleSchema
|
||||||
|
|
||||||
from lemur.authorities.schemas import AuthorityNestedOutputSchema
|
from lemur.authorities.schemas import AuthorityNestedOutputSchema
|
||||||
from lemur.destinations.schemas import DestinationNestedOutputSchema
|
from lemur.destinations.schemas import DestinationNestedOutputSchema
|
||||||
from lemur.notifications.schemas import NotificationNestedOutputSchema
|
from lemur.notifications.schemas import NotificationNestedOutputSchema
|
||||||
|
from lemur.roles.schemas import RoleNestedOutputSchema
|
||||||
# from lemur.domains.schemas import DomainNestedOutputSchema
|
# from lemur.domains.schemas import DomainNestedOutputSchema
|
||||||
from lemur.users.schemas import UserNestedOutputSchema
|
from lemur.users.schemas import UserNestedOutputSchema
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ class CertificateInputSchema(CertificateSchema):
|
||||||
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
||||||
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
|
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
|
||||||
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
|
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
|
||||||
|
roles = fields.Nested(AssociatedRoleSchema, missing=[], many=True)
|
||||||
|
|
||||||
csr = fields.String(validate=validators.csr)
|
csr = fields.String(validate=validators.csr)
|
||||||
|
|
||||||
|
@ -73,6 +75,7 @@ class CertificateEditInputSchema(CertificateSchema):
|
||||||
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
||||||
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
|
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
|
||||||
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
|
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
|
||||||
|
roles = fields.Nested(AssociatedRoleSchema, missing=[], many=True)
|
||||||
|
|
||||||
|
|
||||||
class CertificateNestedOutputSchema(LemurOutputSchema):
|
class CertificateNestedOutputSchema(LemurOutputSchema):
|
||||||
|
@ -117,6 +120,7 @@ class CertificateOutputSchema(LemurOutputSchema):
|
||||||
notifications = fields.Nested(NotificationNestedOutputSchema, many=True)
|
notifications = fields.Nested(NotificationNestedOutputSchema, many=True)
|
||||||
replaces = fields.Nested(CertificateNestedOutputSchema, many=True)
|
replaces = fields.Nested(CertificateNestedOutputSchema, many=True)
|
||||||
authority = fields.Nested(AuthorityNestedOutputSchema)
|
authority = fields.Nested(AuthorityNestedOutputSchema)
|
||||||
|
roles = fields.Nested(RoleNestedOutputSchema, many=True)
|
||||||
|
|
||||||
|
|
||||||
class CertificateUploadInputSchema(CertificateSchema):
|
class CertificateUploadInputSchema(CertificateSchema):
|
||||||
|
@ -130,6 +134,7 @@ class CertificateUploadInputSchema(CertificateSchema):
|
||||||
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
||||||
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
|
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
|
||||||
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
|
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
|
||||||
|
roles = fields.Nested(AssociatedRoleSchema, missing=[], many=True)
|
||||||
|
|
||||||
@validates_schema
|
@validates_schema
|
||||||
def keys(self, data):
|
def keys(self, data):
|
||||||
|
|
|
@ -91,7 +91,7 @@ def export(cert, export_plugin):
|
||||||
return plugin.export(cert.body, cert.chain, cert.private_key, export_plugin['pluginOptions'])
|
return plugin.export(cert.body, cert.chain, cert.private_key, export_plugin['pluginOptions'])
|
||||||
|
|
||||||
|
|
||||||
def update(cert_id, owner, description, active, destinations, notifications, replaces):
|
def update(cert_id, owner, description, active, destinations, notifications, replaces, roles):
|
||||||
"""
|
"""
|
||||||
Updates a certificate
|
Updates a certificate
|
||||||
:param cert_id:
|
:param cert_id:
|
||||||
|
@ -107,6 +107,8 @@ def update(cert_id, owner, description, active, destinations, notifications, rep
|
||||||
cert.active = active
|
cert.active = active
|
||||||
cert.description = description
|
cert.description = description
|
||||||
cert.destinations = destinations
|
cert.destinations = destinations
|
||||||
|
cert.notifications = notifications
|
||||||
|
cert.roles = roles
|
||||||
cert.replaces = replaces
|
cert.replaces = replaces
|
||||||
cert.owner = owner
|
cert.owner = owner
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ from lemur.common.schema import validate_schema
|
||||||
from lemur.common.utils import paginated_parser
|
from lemur.common.utils import paginated_parser
|
||||||
|
|
||||||
from lemur.auth.service import AuthenticatedResource
|
from lemur.auth.service import AuthenticatedResource
|
||||||
from lemur.auth.permissions import ViewKeyPermission, AuthorityPermission, UpdateCertificatePermission
|
from lemur.auth.permissions import ViewKeyPermission, AuthorityPermission, CertificatePermission
|
||||||
|
|
||||||
from lemur.certificates import service
|
from lemur.certificates import service
|
||||||
from lemur.certificates.schemas import certificate_input_schema, certificate_output_schema, \
|
from lemur.certificates.schemas import certificate_input_schema, certificate_output_schema, \
|
||||||
|
@ -519,9 +519,8 @@ class Certificates(AuthenticatedResource):
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
cert = service.get(certificate_id)
|
cert = service.get(certificate_id)
|
||||||
role = role_service.get_by_name(cert.owner)
|
|
||||||
|
|
||||||
permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None))
|
permission = CertificatePermission(cert.id, [x.name for x in cert.roles])
|
||||||
|
|
||||||
if permission.can():
|
if permission.can():
|
||||||
return service.update(
|
return service.update(
|
||||||
|
@ -531,7 +530,8 @@ class Certificates(AuthenticatedResource):
|
||||||
data['active'],
|
data['active'],
|
||||||
data['destinations'],
|
data['destinations'],
|
||||||
data['notifications'],
|
data['notifications'],
|
||||||
data['replacements']
|
data['replacements'],
|
||||||
|
data['roles']
|
||||||
)
|
)
|
||||||
|
|
||||||
return dict(message='You are not authorized to update this certificate'), 403
|
return dict(message='You are not authorized to update this certificate'), 403
|
||||||
|
@ -742,8 +742,8 @@ class CertificateExport(AuthenticatedResource):
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
cert = service.get(certificate_id)
|
cert = service.get(certificate_id)
|
||||||
role = role_service.get_by_name(cert.owner)
|
|
||||||
permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None))
|
permission = CertificatePermission(cert.id, [x.name for x in cert.roles])
|
||||||
|
|
||||||
options = data['plugin']['plugin_options']
|
options = data['plugin']['plugin_options']
|
||||||
plugin = data['plugin']['plugin_object']
|
plugin = data['plugin']['plugin_object']
|
||||||
|
|
|
@ -30,6 +30,13 @@ class RoleOutputSchema(LemurOutputSchema):
|
||||||
users = fields.Nested(UserNestedOutputSchema, many=True)
|
users = fields.Nested(UserNestedOutputSchema, many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class RoleNestedOutputSchema(LemurOutputSchema):
|
||||||
|
__envelope__ = False
|
||||||
|
id = fields.Integer()
|
||||||
|
name = fields.String()
|
||||||
|
description = fields.String()
|
||||||
|
|
||||||
|
|
||||||
role_input_schema = RoleInputSchema()
|
role_input_schema = RoleInputSchema()
|
||||||
role_output_schema = RoleOutputSchema()
|
role_output_schema = RoleOutputSchema()
|
||||||
roles_output_schema = RoleOutputSchema(many=True)
|
roles_output_schema = RoleOutputSchema(many=True)
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
|
|
||||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
"""
|
"""
|
||||||
from flask import g
|
|
||||||
|
|
||||||
from lemur import database
|
from lemur import database
|
||||||
from lemur.roles.models import Role
|
from lemur.roles.models import Role
|
||||||
from lemur.users.models import User
|
from lemur.users.models import User
|
||||||
|
@ -102,13 +100,6 @@ def render(args):
|
||||||
if authority_id:
|
if authority_id:
|
||||||
query = query.filter(Role.authority_id == authority_id)
|
query = query.filter(Role.authority_id == authority_id)
|
||||||
|
|
||||||
# we make sure that user can see the role - admins can see all
|
|
||||||
if not g.current_user.is_admin:
|
|
||||||
ids = []
|
|
||||||
for role in g.current_user.roles:
|
|
||||||
ids.append(role.id)
|
|
||||||
query = query.filter(Role.id.in_(ids))
|
|
||||||
|
|
||||||
if filt:
|
if filt:
|
||||||
terms = filt.split(';')
|
terms = filt.split(';')
|
||||||
query = database.filter(query, Role, terms)
|
query = database.filter(query, Role, terms)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<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>Edit <span class="text-muted"><small>{{ authority.name }}</small></span></h3>
|
<h3>Edit <span class="text-muted"><small>{{ authority.name }}</small></span></h3>
|
||||||
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
||||||
<div class="form-group"
|
<div class="form-group"
|
||||||
|
@ -22,41 +24,23 @@
|
||||||
Description
|
Description
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<textarea name="description" ng-model="authority.description" placeholder="Something elegant" class="form-control" required></textarea>
|
<textarea name="description" ng-model="authority.description" placeholder="Something elegant"
|
||||||
<p ng-show="editForm.description.$invalid && !editForm.description.$pristine" class="help-block">You must give a short description about this authority will be used for, this description should only include alphanumeric characters</p>
|
class="form-control" required></textarea>
|
||||||
|
<p ng-show="editForm.description.$invalid && !editForm.description.$pristine" class="help-block">You
|
||||||
|
must give a short description about this authority will be used for, this description should only
|
||||||
|
include alphanumeric characters</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-2">
|
<label class="control-label col-sm-2">
|
||||||
Roles
|
Roles
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10" ng-model="authority" role-select></div>
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" ng-model="authority.selectedRole" placeholder="Role Name"
|
|
||||||
uib-typeahead="role.name for role in roleService.findRoleByName($viewValue)" typeahead-loading="loadingRoles"
|
|
||||||
class="form-control input-md" typeahead-on-select="authority.attachRole($item)" typeahead-wait-ms="500"
|
|
||||||
uib-tooltip="Roles control which authorities a user can issue certificates from"
|
|
||||||
tooltip-trigger="focus" tooltip-placement="top">
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button ng-model="roles.show" class="btn btn-md btn-default" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
|
||||||
<span class="badge">{{ authority.roles.length || 0 }}</span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<table ng-show="authority.roles" class="table">
|
|
||||||
<tr ng-repeat="role in authority.roles track by $index">
|
|
||||||
<td><a class="btn btn-sm btn-info" href="#/roles/{{ role.id }}/edit">{{ role.name }}</a></td>
|
|
||||||
<td><span class="text-muted">{{ role.description }}</span></td>
|
|
||||||
<td>
|
|
||||||
<button type="button" ng-click="authority.removeRole($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button ng-click="save(authority)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save</button>
|
<button ng-click="save(authority)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save
|
||||||
|
</button>
|
||||||
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
|
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -120,6 +120,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">
|
||||||
|
Roles
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10" ng-model="authority" role-select></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,12 @@
|
||||||
<p ng-show="editForm.description.$invalid && !editForm.description.$pristine" class="help-block">You must give a short description about this authority will be used for, this description should only include alphanumeric characters</p>
|
<p ng-show="editForm.description.$invalid && !editForm.description.$pristine" class="help-block">You must give a short description about this authority will be used for, this description should only include alphanumeric characters</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">
|
||||||
|
Roles
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10" ng-model="certificate" role-select></div>
|
||||||
|
</div>
|
||||||
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
||||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||||
|
|
|
@ -146,6 +146,12 @@
|
||||||
class="help-block">Enter a valid certificate signing request.</p>
|
class="help-block">Enter a valid certificate signing request.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">
|
||||||
|
Roles
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10" ng-model="certificate" role-select></div>
|
||||||
|
</div>
|
||||||
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
||||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||||
|
|
|
@ -81,6 +81,12 @@
|
||||||
class="help-block">Enter a valid certificate.</p>
|
class="help-block">Enter a valid certificate.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">
|
||||||
|
Roles
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10" ng-model="certificate" role-select></div>
|
||||||
|
</div>
|
||||||
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
||||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||||
|
|
|
@ -4,6 +4,16 @@ angular.module('lemur')
|
||||||
.service('CertificateApi', function (LemurRestangular, DomainService) {
|
.service('CertificateApi', function (LemurRestangular, DomainService) {
|
||||||
LemurRestangular.extendModel('certificates', function (obj) {
|
LemurRestangular.extendModel('certificates', function (obj) {
|
||||||
return angular.extend(obj, {
|
return angular.extend(obj, {
|
||||||
|
attachRole: function (role) {
|
||||||
|
this.selectedRole = null;
|
||||||
|
if (this.roles === undefined) {
|
||||||
|
this.roles = [];
|
||||||
|
}
|
||||||
|
this.roles.push(role);
|
||||||
|
},
|
||||||
|
removeRole: function (index) {
|
||||||
|
this.roles.splice(index, 1);
|
||||||
|
},
|
||||||
attachAuthority: function (authority) {
|
attachAuthority: function (authority) {
|
||||||
this.authority = authority;
|
this.authority = authority;
|
||||||
this.authority.maxDate = moment(this.authority.notAfter).subtract(1, 'days').format('YYYY/MM/DD');
|
this.authority.maxDate = moment(this.authority.notAfter).subtract(1, 'days').format('YYYY/MM/DD');
|
||||||
|
|
|
@ -121,6 +121,15 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
|
<uib-tab>
|
||||||
|
<uib-tab-heading>Roles</uib-tab-heading>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item" ng-repeat="role in certificate.roles">
|
||||||
|
<strong>{{ role.name }}</strong>
|
||||||
|
<span class="pull-right">{{ role.description}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</uib-tab>
|
||||||
<uib-tab>
|
<uib-tab>
|
||||||
<uib-tab-heading>Destinations</uib-tab-heading>
|
<uib-tab-heading>Destinations</uib-tab-heading>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
|
|
|
@ -1,7 +1,29 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('lemur')
|
angular.module('lemur')
|
||||||
|
.directive('roleSelect', function (RoleApi) {
|
||||||
|
return {
|
||||||
|
restrict: 'AE',
|
||||||
|
scope: {
|
||||||
|
ngModel: '='
|
||||||
|
},
|
||||||
|
replace: true,
|
||||||
|
require: 'ngModel',
|
||||||
|
templateUrl: '/angular/roles/role/roleSelect.tpl.html',
|
||||||
|
link: function postLink($scope) {
|
||||||
|
RoleApi.getList().then(function (roles) {
|
||||||
|
$scope.roles = roles;
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.findRoleByName = function (search) {
|
||||||
|
return RoleApi.getList({'filter[name]': search})
|
||||||
|
.then(function (roles) {
|
||||||
|
return roles;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
.controller('RolesEditController', function ($scope, $uibModalInstance, RoleApi, RoleService, UserService, toaster, editId) {
|
.controller('RolesEditController', function ($scope, $uibModalInstance, RoleApi, RoleService, UserService, toaster, editId) {
|
||||||
RoleApi.get(editId).then(function (role) {
|
RoleApi.get(editId).then(function (role) {
|
||||||
$scope.role = role;
|
$scope.role = role;
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" ng-model="ngModel.selectedRole" placeholder="Role Name"
|
||||||
|
uib-typeahead="role.name for role in findRoleByName($viewValue)" typeahead-loading="loadingRoles"
|
||||||
|
class="form-control input-md" typeahead-on-select="ngModel.attachRole($item)" typeahead-wait-ms="500"
|
||||||
|
uib-tooltip="Roles control who can access this resource"
|
||||||
|
tooltip-trigger="focus" tooltip-placement="top">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button ng-model="roles.show" class="btn btn-md btn-default" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||||
|
<span class="badge">{{ ngModel.roles.length || 0 }}</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<table ng-show="ngModel.roles" class="table">
|
||||||
|
<tr ng-repeat="role in ngModel.roles track by $index">
|
||||||
|
<td><a class="btn btn-sm btn-info" href="#/roles/{{ role.id }}/edit">{{ role.name }}</a></td>
|
||||||
|
<td><span class="text-muted">{{ role.description }}</span></td>
|
||||||
|
<td>
|
||||||
|
<button type="button" ng-click="ngModel.removeRole($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
|
@ -108,7 +108,7 @@ def test_certificate_input_schema(client, authority):
|
||||||
assert data['country'] == 'US'
|
assert data['country'] == 'US'
|
||||||
assert data['location'] == 'Los Gatos'
|
assert data['location'] == 'Los Gatos'
|
||||||
|
|
||||||
assert len(data.keys()) == 12
|
assert len(data.keys()) == 13
|
||||||
|
|
||||||
|
|
||||||
def test_certificate_input_with_extensions(client, authority):
|
def test_certificate_input_with_extensions(client, authority):
|
||||||
|
|
Loading…
Reference in New Issue