* Closes #147

* Fixing tests

* Ensuring we can validate max dates.
This commit is contained in:
kevgliss 2016-05-23 11:28:25 -07:00
parent bd727b825d
commit 656269ff17
22 changed files with 334 additions and 200 deletions

View File

@ -6,49 +6,35 @@
: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 cryptography import x509
from cryptography.hazmat.backends import default_backend
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, Text, func, ForeignKey, DateTime, PassiveDefault, Boolean from sqlalchemy import Column, Integer, String, Text, func, ForeignKey, DateTime, PassiveDefault, Boolean
from sqlalchemy.dialects.postgresql import JSON from sqlalchemy.dialects.postgresql import JSON
from lemur.database import db from lemur.database import db
from lemur.models import roles_authorities from lemur.models import roles_authorities
from lemur.common import defaults
class Authority(db.Model): class Authority(db.Model):
__tablename__ = 'authorities' __tablename__ = 'authorities'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
owner = Column(String(128)) owner = Column(String(128), nullable=False)
name = Column(String(128), unique=True) name = Column(String(128), unique=True)
body = Column(Text()) body = Column(Text())
chain = Column(Text()) chain = Column(Text())
bits = Column(Integer())
cn = Column(String(128))
not_before = Column(DateTime)
not_after = Column(DateTime)
active = Column(Boolean, default=True) active = Column(Boolean, default=True)
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
plugin_name = Column(String(64)) plugin_name = Column(String(64))
description = Column(Text) description = Column(Text)
options = Column(JSON) options = Column(JSON)
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
roles = relationship('Role', secondary=roles_authorities, passive_deletes=True, backref=db.backref('authority'), lazy='dynamic') roles = relationship('Role', secondary=roles_authorities, passive_deletes=True, backref=db.backref('authority'), lazy='dynamic')
user_id = Column(Integer, ForeignKey('users.id')) user_id = Column(Integer, ForeignKey('users.id'))
certificates = relationship("Certificate", backref='authority') authority_certificate = relationship("Certificate", backref='root_authority', uselist=False, foreign_keys='Certificate.root_authority_id')
certificates = relationship("Certificate", backref='authority', foreign_keys='Certificate.authority_id')
def __init__(self, name, owner, plugin_name, body, roles=None, chain=None, description=None): def __init__(self, **kwargs):
cert = x509.load_pem_x509_certificate(bytes(body), default_backend()) self.owner = kwargs['owner']
self.name = name self.roles = kwargs.get('roles', [])
self.body = body self.name = kwargs.get('name')
self.chain = chain self.description = kwargs.get('description')
self.owner = owner self.authority_certificate = kwargs['authority_certificate']
self.description = description self.plugin_name = kwargs['plugin']['slug']
self.plugin_name = plugin_name
self.cn = defaults.common_name(cert)
self.not_before = defaults.not_before(cert)
self.not_after = defaults.not_after(cert)
if roles:
self.roles = roles

View File

@ -12,6 +12,7 @@ from marshmallow import validate
from marshmallow.exceptions import ValidationError from marshmallow.exceptions import ValidationError
from lemur.schemas import PluginInputSchema, PluginOutputSchema, ExtensionSchema, AssociatedAuthoritySchema, AssociatedRoleSchema from lemur.schemas import PluginInputSchema, PluginOutputSchema, ExtensionSchema, AssociatedAuthoritySchema, AssociatedRoleSchema
from lemur.users.schemas import UserNestedOutputSchema
from lemur.common.schema import LemurInputSchema, LemurOutputSchema from lemur.common.schema import LemurInputSchema, LemurOutputSchema
from lemur.common import validators from lemur.common import validators
@ -61,25 +62,39 @@ class AuthorityInputSchema(LemurInputSchema):
class AuthorityUpdateSchema(LemurInputSchema): class AuthorityUpdateSchema(LemurInputSchema):
owner = fields.Email() owner = fields.Email(required=True)
description = fields.String() description = fields.String()
active = fields.Boolean() active = fields.Boolean()
roles = fields.Nested(AssociatedRoleSchema(many=True)) roles = fields.Nested(AssociatedRoleSchema(many=True))
class RootAuthorityCertificateOutputSchema(LemurOutputSchema):
__envelope__ = False
id = fields.Integer()
active = fields.Boolean()
bits = fields.Integer()
body = fields.String()
chain = fields.String()
description = fields.String()
name = fields.String()
cn = fields.String()
not_after = fields.DateTime()
not_before = fields.DateTime()
owner = fields.Email()
status = fields.Boolean()
user = fields.Nested(UserNestedOutputSchema)
class AuthorityOutputSchema(LemurOutputSchema): class AuthorityOutputSchema(LemurOutputSchema):
id = fields.Integer() id = fields.Integer()
description = fields.String() description = fields.String()
name = fields.String() name = fields.String()
owner = fields.Email() owner = fields.Email()
not_before = fields.DateTime()
not_after = fields.DateTime()
plugin = fields.Nested(PluginOutputSchema) plugin = fields.Nested(PluginOutputSchema)
body = fields.String()
chain = fields.String()
active = fields.Boolean() active = fields.Boolean()
options = fields.Dict() options = fields.Dict()
roles = fields.List(fields.Nested(AssociatedRoleSchema)) roles = fields.List(fields.Nested(AssociatedRoleSchema))
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema)
class AuthorityNestedOutputSchema(LemurOutputSchema): class AuthorityNestedOutputSchema(LemurOutputSchema):
@ -87,13 +102,8 @@ class AuthorityNestedOutputSchema(LemurOutputSchema):
description = fields.String() description = fields.String()
name = fields.String() name = fields.String()
owner = fields.Email() owner = fields.Email()
not_before = fields.DateTime()
not_after = fields.DateTime()
plugin = fields.Nested(PluginOutputSchema) plugin = fields.Nested(PluginOutputSchema)
body = fields.String()
chain = fields.String()
active = fields.Boolean() active = fields.Boolean()
options = fields.Dict()
authority_update_schema = AuthorityUpdateSchema() authority_update_schema = AuthorityUpdateSchema()

View File

@ -9,14 +9,13 @@
""" """
from flask import g from flask import g
from flask import current_app
from lemur import database from lemur import database
from lemur.extensions import metrics
from lemur.authorities.models import Authority from lemur.authorities.models import Authority
from lemur.roles import service as role_service from lemur.roles import service as role_service
from lemur.notifications import service as notification_service
from lemur.certificates.models import Certificate from lemur.certificates.service import upload
def update(authority_id, description=None, owner=None, active=None, roles=None): def update(authority_id, description=None, owner=None, active=None, roles=None):
@ -40,42 +39,28 @@ def update(authority_id, description=None, owner=None, active=None, roles=None):
return database.update(authority) return database.update(authority)
def create(kwargs): def mint(**kwargs):
""" """
Create a new authority. Creates the authority based on the plugin provided.
"""
issuer = kwargs['plugin']['plugin_object']
body, chain, roles = issuer.create_authority(kwargs)
return body, chain, roles
def create_authority_roles(roles, **kwargs):
"""
Creates all of the necessary authority roles.
:param roles:
:param kwargs:
:return: :return:
""" """
issuer = kwargs['plugin']['plugin_object']
kwargs['creator'] = g.current_user.email
cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs)
cert = Certificate(body=cert_body, chain=intermediate, **kwargs)
if kwargs['type'] == 'subca':
cert.description = "This is the ROOT certificate for the {0} sub certificate authority the parent \
authority is {1}.".format(kwargs.get('name'), kwargs.get('parent'))
else:
cert.description = "This is the ROOT certificate for the {0} certificate authority.".format(
kwargs.get('name')
)
cert.user = g.current_user
cert.notifications = notification_service.create_default_expiration_notifications(
'DEFAULT_SECURITY',
current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')
)
# we create and attach any roles that the issuer gives us
role_objs = [] role_objs = []
for r in issuer_roles: for r in roles:
role = role_service.create( role = role_service.create(
r['name'], r['name'],
password=r['password'], password=r['password'],
description="{0} auto generated role".format(issuer.title), description="Auto generated role for {0}".format(kwargs['plugin']['plugin_object'].title),
username=r['username']) username=r['username'])
# the user creating the authority should be able to administer it # the user creating the authority should be able to administer it
@ -93,22 +78,39 @@ def create(kwargs):
) )
role_objs.append(owner_role) role_objs.append(owner_role)
return role_objs
authority = Authority(
kwargs.get('name'),
kwargs['owner'],
issuer.slug,
cert_body,
description=kwargs['description'],
chain=intermediate,
roles=role_objs
)
database.update(cert) def create(**kwargs):
"""
Creates a new authority.
"""
kwargs['creator'] = g.user.email
body, chain, roles = mint(**kwargs)
kwargs['body'] = body
kwargs['chain'] = chain
kwargs['roles'] = create_authority_roles(roles, **kwargs)
if kwargs['type'] == 'subca':
description = "This is the ROOT certificate for the {0} sub certificate authority the parent \
authority is {1}.".format(kwargs.get('name'), kwargs.get('parent'))
else:
description = "This is the ROOT certificate for the {0} certificate authority.".format(
kwargs.get('name')
)
kwargs['description'] = description
cert = upload(**kwargs)
kwargs['authority_certificate'] = cert
authority = Authority(**kwargs)
authority = database.create(authority) authority = database.create(authority)
g.user.authorities.append(authority)
g.current_user.authorities.append(authority) metrics.send('authority_created', 'counter', 1, metric_tags=dict(owner=authority.owner))
return authority return authority

View File

@ -167,7 +167,7 @@ class AuthoritiesList(AuthenticatedResource):
:statuscode 403: unauthenticated :statuscode 403: unauthenticated
:statuscode 200: no error :statuscode 200: no error
""" """
return service.create(data) return service.create(**data)
class Authorities(AuthenticatedResource): class Authorities(AuthenticatedResource):

View File

@ -36,7 +36,7 @@ class Certificate(db.Model):
__tablename__ = 'certificates' __tablename__ = 'certificates'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
owner = Column(String(128), nullable=False) owner = Column(String(128), nullable=False)
name = Column(String(128), unique=True) name = Column(String(128)) # , unique=True) TODO make all names unique
description = Column(String(1024)) description = Column(String(1024))
active = Column(Boolean, default=True) active = Column(Boolean, default=True)
@ -59,7 +59,9 @@ class Certificate(db.Model):
san = Column(String(1024)) # TODO this should be migrated to boolean san = Column(String(1024)) # TODO this should be migrated to boolean
user_id = Column(Integer, ForeignKey('users.id')) user_id = Column(Integer, ForeignKey('users.id'))
authority_id = Column(Integer, ForeignKey('authorities.id')) authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE"))
root_authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE"))
notifications = relationship("Notification", secondary=certificate_notification_associations, backref='certificate') notifications = relationship("Notification", secondary=certificate_notification_associations, backref='certificate')
destinations = relationship("Destination", secondary=certificate_destination_associations, backref='certificate') destinations = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
sources = relationship("Source", secondary=certificate_source_associations, backref='certificate') sources = relationship("Source", secondary=certificate_source_associations, backref='certificate')

View File

@ -6,7 +6,6 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
from flask import current_app from flask import current_app
from marshmallow import fields, validates_schema, post_load from marshmallow import fields, validates_schema, post_load
from marshmallow.exceptions import ValidationError from marshmallow.exceptions import ValidationError
@ -17,7 +16,7 @@ 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.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
from lemur.common.schema import LemurInputSchema, LemurOutputSchema from lemur.common.schema import LemurInputSchema, LemurOutputSchema
@ -115,7 +114,7 @@ class CertificateOutputSchema(LemurOutputSchema):
signing_algorithm = fields.String() signing_algorithm = fields.String()
status = fields.Boolean() status = fields.Boolean()
user = fields.Nested(UserNestedOutputSchema) user = fields.Nested(UserNestedOutputSchema)
# domains = fields.Nested(DomainNestedOutputSchema) domains = fields.Nested(DomainNestedOutputSchema)
destinations = fields.Nested(DestinationNestedOutputSchema, many=True) destinations = fields.Nested(DestinationNestedOutputSchema, many=True)
notifications = fields.Nested(NotificationNestedOutputSchema, many=True) notifications = fields.Nested(NotificationNestedOutputSchema, many=True)
replaces = fields.Nested(CertificateNestedOutputSchema, many=True) replaces = fields.Nested(CertificateNestedOutputSchema, many=True)

View File

@ -120,7 +120,6 @@ def mint(**kwargs):
Minting is slightly different for each authority. Minting is slightly different for each authority.
Support for multiple authorities is handled by individual plugins. Support for multiple authorities is handled by individual plugins.
:param issuer_options:
""" """
authority = kwargs['authority'] authority = kwargs['authority']

View File

@ -8,7 +8,7 @@
""" """
from functools import wraps from functools import wraps
from flask import request from flask import request, current_app
from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy.orm.collections import InstrumentedList
@ -135,6 +135,7 @@ def validate_schema(input_schema, output_schema):
try: try:
resp = f(*args, **kwargs) resp = f(*args, **kwargs)
except Exception as e: except Exception as e:
current_app.logger.exception(e)
return dict(message=e.message), 500 return dict(message=e.message), 500
if isinstance(resp, tuple): if isinstance(resp, tuple):

View File

@ -102,19 +102,19 @@ def dates(data):
raise ValidationError('Validity start must be before validity end.') raise ValidationError('Validity start must be before validity end.')
if data.get('authority'): if data.get('authority'):
if data.get('validity_start').replace(tzinfo=None) < data['authority'].not_before: if data.get('validity_start').replace(tzinfo=None) < data['authority'].authority_certificate.not_before:
raise ValidationError('Validity start must not be before {0}'.format(data['authority'].not_before)) raise ValidationError('Validity start must not be before {0}'.format(data['authority'].authority_certificate.not_before))
if data.get('validity_end').replace(tzinfo=None) > data['authority'].not_after: if data.get('validity_end').replace(tzinfo=None) > data['authority'].authority_certificate.not_after:
raise ValidationError('Validity end must not be after {0}'.format(data['authority'].not_after)) raise ValidationError('Validity end must not be after {0}'.format(data['authority'].authority_certificate.not_after))
if data.get('validity_years'): if data.get('validity_years'):
now = arrow.utcnow() now = arrow.utcnow()
end = now.replace(years=+data['validity_years']) end = now.replace(years=+data['validity_years'])
if data.get('authority'): if data.get('authority'):
if now.naive < data['authority'].not_before: if now.naive < data['authority'].authority_certificate.not_before:
raise ValidationError('Validity start must not be before {0}'.format(data['authority'].not_before)) raise ValidationError('Validity start must not be before {0}'.format(data['authority'].authority_certificate.not_before))
if end.naive > data['authority'].not_after: if end.naive > data['authority'].authority_certificate.not_after:
raise ValidationError('Validity end must not be after {0}'.format(data['authority'].not_after)) raise ValidationError('Validity end must not be after {0}'.format(data['authority'].authority_certificate.not_after))

View File

@ -9,7 +9,7 @@ from marshmallow import fields
from lemur.common.schema import LemurInputSchema, LemurOutputSchema from lemur.common.schema import LemurInputSchema, LemurOutputSchema
from lemur.schemas import AssociatedCertificateSchema from lemur.schemas import AssociatedCertificateSchema
from lemur.certificates.schemas import CertificateNestedOutputSchema # from lemur.certificates.schemas import CertificateNestedOutputSchema
class DomainInputSchema(LemurInputSchema): class DomainInputSchema(LemurInputSchema):
@ -23,7 +23,7 @@ class DomainOutputSchema(LemurOutputSchema):
id = fields.Integer() id = fields.Integer()
name = fields.String() name = fields.String()
sensitive = fields.Boolean() sensitive = fields.Boolean()
certificates = fields.Nested(CertificateNestedOutputSchema, many=True, missing=[]) # certificates = fields.Nested(CertificateNestedOutputSchema, many=True, missing=[])
class DomainNestedOutputSchema(DomainOutputSchema): class DomainNestedOutputSchema(DomainOutputSchema):

View File

@ -0,0 +1,58 @@
"""empty message
Revision ID: 3307381f3b88
Revises: 412b22cb656a
Create Date: 2016-05-20 17:33:04.360687
"""
# revision identifiers, used by Alembic.
revision = '3307381f3b88'
down_revision = '412b22cb656a'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.alter_column('authorities', 'owner',
existing_type=sa.VARCHAR(length=128),
nullable=False)
op.drop_column('authorities', 'not_after')
op.drop_column('authorities', 'bits')
op.drop_column('authorities', 'cn')
op.drop_column('authorities', 'not_before')
op.add_column('certificates', sa.Column('root_authority_id', sa.Integer(), nullable=True))
op.alter_column('certificates', 'body',
existing_type=sa.TEXT(),
nullable=False)
op.alter_column('certificates', 'owner',
existing_type=sa.VARCHAR(length=128),
nullable=False)
op.drop_constraint(u'certificates_authority_id_fkey', 'certificates', type_='foreignkey')
op.create_foreign_key(None, 'certificates', 'authorities', ['authority_id'], ['id'], ondelete='CASCADE')
op.create_foreign_key(None, 'certificates', 'authorities', ['root_authority_id'], ['id'], ondelete='CASCADE')
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'certificates', type_='foreignkey')
op.drop_constraint(None, 'certificates', type_='foreignkey')
op.create_foreign_key(u'certificates_authority_id_fkey', 'certificates', 'authorities', ['authority_id'], ['id'])
op.alter_column('certificates', 'owner',
existing_type=sa.VARCHAR(length=128),
nullable=True)
op.alter_column('certificates', 'body',
existing_type=sa.TEXT(),
nullable=True)
op.drop_column('certificates', 'root_authority_id')
op.add_column('authorities', sa.Column('not_before', postgresql.TIMESTAMP(), autoincrement=False, nullable=True))
op.add_column('authorities', sa.Column('cn', sa.VARCHAR(length=128), autoincrement=False, nullable=True))
op.add_column('authorities', sa.Column('bits', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('authorities', sa.Column('not_after', postgresql.TIMESTAMP(), autoincrement=False, nullable=True))
op.alter_column('authorities', 'owner',
existing_type=sa.VARCHAR(length=128),
nullable=True)
### end Alembic commands ###

View File

@ -1,4 +1,4 @@
"""empty message """
Revision ID: 412b22cb656a Revision ID: 412b22cb656a
Revises: 4c50b903d1ae Revises: 4c50b903d1ae

View File

@ -9,17 +9,15 @@
""" """
from marshmallow import fields, post_load, pre_load, post_dump, validates_schema from marshmallow import fields, post_load, pre_load, post_dump, validates_schema
from lemur.roles.models import Role
from lemur.authorities.models import Authority from lemur.authorities.models import Authority
from lemur.destinations.models import Destination
from lemur.certificates.models import Certificate from lemur.certificates.models import Certificate
from lemur.notifications.models import Notification
from lemur.users.models import User
from lemur.common import validators from lemur.common import validators
from lemur.common.schema import LemurSchema, LemurInputSchema, LemurOutputSchema from lemur.common.schema import LemurSchema, LemurInputSchema, LemurOutputSchema
from lemur.destinations.models import Destination
from lemur.notifications.models import Notification
from lemur.plugins import plugins from lemur.plugins import plugins
from lemur.roles.models import Role
from lemur.users.models import User
class AssociatedAuthoritySchema(LemurInputSchema): class AssociatedAuthoritySchema(LemurInputSchema):

View File

@ -93,8 +93,8 @@
is-open="popup1.opened" is-open="popup1.opened"
datepicker-options="dateOptions" datepicker-options="dateOptions"
close-text="Close" close-text="Close"
max-date="authority.authority.notAfter" max-date="authority.authority.authorityCertificate.notAfter"
min-date="authority.authority.notBefore" min-date="authority.authority.authorityCertificate.notBefore"
alt-input-formats="altInputFormats" /> alt-input-formats="altInputFormats" />
<span class="input-group-btn"> <span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button> <button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>

View File

@ -16,7 +16,7 @@ angular.module('lemur')
}); });
}) })
.controller('AuthoritiesViewController', function ($scope, $q, $uibModal, $stateParams, AuthorityApi, AuthorityService, ngTableParams, toaster) { .controller('AuthoritiesViewController', function ($scope, $q, $uibModal, $stateParams, AuthorityApi, AuthorityService, MomentService, ngTableParams, toaster) {
$scope.filter = $stateParams; $scope.filter = $stateParams;
$scope.authoritiesTable = new ngTableParams({ $scope.authoritiesTable = new ngTableParams({
page: 1, // show first page page: 1, // show first page
@ -35,6 +35,8 @@ angular.module('lemur')
} }
}); });
$scope.momentService = MomentService;
$scope.updateActive = function (authority) { $scope.updateActive = function (authority) {
AuthorityService.updateActive(authority).then( AuthorityService.updateActive(authority).then(
function () { function () {

View File

@ -1,51 +1,120 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h2 class="featurette-heading">Authorities <h2 class="featurette-heading">Authorities
<span class="text-muted"><small>The nail that sticks out farthest gets hammered the hardest</small></span></h2> <span class="text-muted"><small>The nail that sticks out farthest gets hammered the hardest</small></span>
<div class="panel panel-default"> </h2>
<div class="panel-heading"> <div class="panel panel-default">
<div class="btn-group pull-right"> <div class="panel-heading">
<button class="btn btn-primary" ng-click="create()">Create</button> <div class="btn-group pull-right">
<button class="btn btn-primary" ng-click="create()">Create</button>
</div>
<div class="btn-group">
<button ng-click="toggleFilter(authoritiesTable)" class="btn btn-default">Filter</button>
</div>
<div class="clearfix"></div>
</div>
<div class="table-responsive">
<table ng-table="authoritiesTable" class="table table-striped" template-pagination="angular/pager.html"
show-filter="false">
<tbody>
<tr ng-repeat-start="authority in $data track by $index">
<td data-title="'Name'" sortable="'name'" filter="{ 'name': 'text' }">
<ul class="list-unstyled">
<li>{{ authority.name }}</li>
<li><span class="text-muted">{{ authority.owner }}</span></li>
</ul>
</td>
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getAuthorityStatus()">
<form>
<switch ng-change="updateActive(authority)" id="status" name="status"
ng-model="authority.active" class="green small"></switch>
</form>
</td>
<td data-title="'Common Name'" filter="{ 'cn': 'text'}">
{{ authority.authorityCertificate.cn }}
</td>
<td data-title="''">
<div class="btn-group pull-right">
<a class="btn btn-sm btn-default"
ui-sref="authority({name: authority.name})">Permalink</a>
<button ng-model="authority.toggle" class="btn btn-sm btn-info" uib-btn-checkbox
btn-checkbox-true="1"
btn-checkbox-false="0">More
</button>
<button uib-tooltip="Edit Authority" ng-click="edit(authority.id)"
class="btn btn-sm btn-warning">
Edit
</button>
</div>
</td>
</tr>
<tr class="warning" ng-if="authority.toggle" ng-repeat-end>
<td colspan="12">
<uib-tabset justified="true" class="col-md-6">
<uib-tab>
<uib-tab-heading>Basic Info</uib-tab-heading>
<ul class="list-group">
<li class="list-group-item">
<strong>Creator</strong>
<span class="pull-right">
{{ authority.authorityCertificate.user.email }}
</span>
</li>
<li class="list-group-item">
<strong>Not Before</strong>
<span class="pull-right" uib-tooltip="{{ authority.authorityCertificate.notBefore }}">
{{ momentService.createMoment(authority.authorityCertificate.notBefore) }}
</span>
</li>
<li class="list-group-item">
<strong>Not After</strong>
<span class="pull-right" uib-tooltip="{{ authority.authorityCertificate.notAfter }}">
{{ momentService.createMoment(authority.authorityCertificate.notAfter) }}
</span>
</li>
<li class="list-group-item">
<strong>Description</strong>
<p>{{ authority.description }}</p>
</li>
</ul>
</uib-tab>
<uib-tab>
<uib-tab-heading>Roles</uib-tab-heading>
<ul class="list-group">
<li class="list-group-item" ng-repeat="role in authority.roles">
<strong>{{ role.name }}</strong>
<span class="pull-right">{{ role.description }}</span>
</li>
</ul>
</uib-tab>
</uib-tabset>
<uib-tabset justified="true" class="col-md-6">
<uib-tab>
<uib-tab-heading>
Chain
<button class="btn btn-xs btn-default clipboard-btn glyphicon glyphicon-copy"
uib-tooltip="Copy chain to clipboard" tooltip-trigger="mouseenter"
clipboard
text="authority.authorityCertificate.chain"></button>
</uib-tab-heading>
<pre style="width: 100%">{{ authority.authorityCertificate.chain }}</pre>
</uib-tab>
<uib-tab>
<uib-tab-heading>
Public Certificate
<button class="btn btn-xs btn-default clipboard-btn glyphicon glyphicon-copy"
uib-tooltip="Copy authority to clipboard" tooltip-trigger="mouseenter"
clipboard
text="authority.authorityCertificate.body"></button>
</uib-tab-heading>
<pre style="width: 100%">{{ authority.authorityCertificate.body }}</pre>
</uib-tab>
</uib-tabset>
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
<div class="btn-group">
<button ng-click="toggleFilter(authoritiesTable)" class="btn btn-default">Filter</button>
</div>
<div class="clearfix"></div>
</div>
<div class="table-responsive">
<table ng-table="authoritiesTable" class="table table-striped" template-pagination="angular/pager.html" show-filter="false">
<tbody>
<tr ng-repeat="authority in $data track by $index">
<td data-title="'Name'" sortable="'name'" filter="{ 'name': 'text' }">
<ul class="list-unstyled">
<li>{{ authority.name }}</li>
<li><span class="text-muted">{{ authority.description }}</span></li>
</ul>
</td>
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getAuthorityStatus()">
<form>
<switch ng-change="updateActive(authority)" id="status" name="status" ng-model="authority.active" class="green small"></switch>
</form>
</td>
<td data-title="'Roles'"> <!--filter="{ 'select': 'role' }" filter-data="roleService.getRoleDropDown()">-->
<div class="btn-group">
<a ng-click="editRole(role.id)" ng-repeat="role in authority.roles" class="btn btn-sm btn-danger">
{{ role.name }}
</a>
</div>
</td>
<td data-title="''">
<div class="btn-group pull-right">
<a class="btn btn-sm btn-default" ui-sref="authority({name: authority.name})">Permalink</a>
<button uib-tooltip="Edit Authority" ng-click="edit(authority.id)" class="btn btn-sm btn-info">
Edit
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div>
</div> </div>

View File

@ -102,8 +102,8 @@
is-open="popup1.opened" is-open="popup1.opened"
datepicker-options="dateOptions" datepicker-options="dateOptions"
close-text="Close" close-text="Close"
max-date="certificate.authority.notAfter" max-date="certificate.authority.authorityCertificate.notAfter"
min-date="certificate.authority.notBefore" min-date="certificate.authority.authorityCertificate.notBefore"
alt-input-formats="altInputFormats"/> alt-input-formats="altInputFormats"/>
<span class="input-group-btn"> <span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i <button type="button" class="btn btn-default" ng-click="open1()"><i
@ -121,8 +121,8 @@
is-open="popup2.opened" is-open="popup2.opened"
datepicker-options="dateOptions" datepicker-options="dateOptions"
close-text="Close" close-text="Close"
max-date="certificate.authority.notAfter" max-date="certificate.authority.authorityCertificate.notAfter"
min-date="certificate.authority.notBefore" min-date="certificate.authority.authorityCertificate.notBefore"
alt-input-formats="altInputFormats"/> alt-input-formats="altInputFormats"/>
<span class="input-group-btn"> <span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open2()"><i <button type="button" class="btn btn-default" ng-click="open2()"><i

View File

@ -137,6 +137,7 @@ def issuer_plugin():
from lemur.plugins.base import register from lemur.plugins.base import register
from .plugins.issuer_plugin import TestIssuerPlugin from .plugins.issuer_plugin import TestIssuerPlugin
register(TestIssuerPlugin) register(TestIssuerPlugin)
return TestIssuerPlugin
@pytest.yield_fixture(scope="function") @pytest.yield_fixture(scope="function")
@ -147,7 +148,7 @@ def logged_in_user(app):
@pytest.yield_fixture(scope="function") @pytest.yield_fixture(scope="function")
def logged_in_admin(app): def logged_in_admin(session, app):
with app.test_request_context(): with app.test_request_context():
identity_changed.send(current_app._get_current_object(), identity=Identity(2)) identity_changed.send(current_app._get_current_object(), identity=Identity(2))
yield yield

View File

@ -1,7 +1,7 @@
from datetime import date from datetime import date
from factory import Sequence, post_generation from factory import Sequence, post_generation, SubFactory
from factory.alchemy import SQLAlchemyModelFactory from factory.alchemy import SQLAlchemyModelFactory
from factory.fuzzy import FuzzyChoice, FuzzyText, FuzzyDate from factory.fuzzy import FuzzyChoice, FuzzyText, FuzzyDate
@ -14,7 +14,7 @@ from lemur.notifications.models import Notification
from lemur.users.models import User from lemur.users.models import User
from lemur.roles.models import Role from lemur.roles.models import Role
from .vectors import INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR from .vectors import INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR
class BaseFactory(SQLAlchemyModelFactory): class BaseFactory(SQLAlchemyModelFactory):
@ -26,27 +26,6 @@ class BaseFactory(SQLAlchemyModelFactory):
sqlalchemy_session = db.session sqlalchemy_session = db.session
class AuthorityFactory(BaseFactory):
"""Authority factory."""
name = Sequence(lambda n: 'authority{0}'.format(n))
owner = 'joe@example.com'
plugin_name = 'test-issuer'
body = INTERNAL_VALID_LONG_STR
class Meta:
"""Factory configuration."""
model = Authority
@post_generation
def roles(self, create, extracted, **kwargs):
if not create:
return
if extracted:
for role in extracted:
self.roles.append(role)
class CertificateFactory(BaseFactory): class CertificateFactory(BaseFactory):
"""Certificate factory.""" """Certificate factory."""
name = Sequence(lambda n: 'certificate{0}'.format(n)) name = Sequence(lambda n: 'certificate{0}'.format(n))
@ -135,6 +114,27 @@ class CertificateFactory(BaseFactory):
self.roles.append(domain) self.roles.append(domain)
class AuthorityFactory(BaseFactory):
"""Authority factory."""
name = Sequence(lambda n: 'authority{0}'.format(n))
owner = 'joe@example.com'
plugin = {'slug': 'test-issuer'}
authority_certificate = SubFactory(CertificateFactory)
class Meta:
"""Factory configuration."""
model = Authority
@post_generation
def roles(self, create, extracted, **kwargs):
if not create:
return
if extracted:
for role in extracted:
self.roles.append(role)
class DestinationFactory(BaseFactory): class DestinationFactory(BaseFactory):
"""Destination factory.""" """Destination factory."""
plugin_name = Sequence(lambda n: 'destination{0}'.format(n)) plugin_name = Sequence(lambda n: 'destination{0}'.format(n))

View File

@ -25,14 +25,6 @@ def test_authority_input_schema(client, role):
assert not errors assert not errors
@pytest.mark.parametrize("token, count", [
(VALID_USER_HEADER_TOKEN, 0),
(VALID_ADMIN_HEADER_TOKEN, 1)
])
def test_admin_authority(client, authority, token, count):
assert client.get(api.url_for(AuthoritiesList), headers=token).json['total'] == count
def test_user_authority(session, client, authority, role, user): def test_user_authority(session, client, authority, role, user):
assert client.get(api.url_for(AuthoritiesList), headers=user['token']).json['total'] == 0 assert client.get(api.url_for(AuthoritiesList), headers=user['token']).json['total'] == 0
u = user['user'] u = user['user']
@ -45,9 +37,23 @@ def test_user_authority(session, client, authority, role, user):
assert client.get(api.url_for(AuthoritiesList), headers=user['token']).json['total'] == 0 assert client.get(api.url_for(AuthoritiesList), headers=user['token']).json['total'] == 0
def test_create_authority(issuer_plugin, logged_in_admin):
from lemur.authorities.service import create
authority = create(plugin={'plugin_object': issuer_plugin, 'slug': issuer_plugin.slug}, owner='jim@example.com', type='root')
assert authority.authority_certificate
@pytest.mark.parametrize("token, count", [
(VALID_USER_HEADER_TOKEN, 0),
(VALID_ADMIN_HEADER_TOKEN, 3)
])
def test_admin_authority(client, authority, token, count):
assert client.get(api.url_for(AuthoritiesList), headers=token).json['total'] == count
@pytest.mark.parametrize("token,status", [ @pytest.mark.parametrize("token,status", [
(VALID_USER_HEADER_TOKEN, 404), (VALID_USER_HEADER_TOKEN, 200),
(VALID_ADMIN_HEADER_TOKEN, 404), (VALID_ADMIN_HEADER_TOKEN, 200),
('', 401) ('', 401)
]) ])
def test_authority_get(client, token, status): def test_authority_get(client, token, status):
@ -64,8 +70,8 @@ def test_authority_post(client, token, status):
@pytest.mark.parametrize("token,status", [ @pytest.mark.parametrize("token,status", [
(VALID_USER_HEADER_TOKEN, 404), (VALID_USER_HEADER_TOKEN, 400),
(VALID_ADMIN_HEADER_TOKEN, 404), (VALID_ADMIN_HEADER_TOKEN, 400),
('', 401) ('', 401)
]) ])
def test_authority_put(client, token, status): def test_authority_put(client, token, status):

View File

@ -7,8 +7,8 @@ from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN
@pytest.mark.parametrize("token,status", [ @pytest.mark.parametrize("token,status", [
(VALID_USER_HEADER_TOKEN, 404), (VALID_USER_HEADER_TOKEN, 200),
(VALID_ADMIN_HEADER_TOKEN, 404), (VALID_ADMIN_HEADER_TOKEN, 200),
('', 401) ('', 401)
]) ])
def test_domain_get(client, token, status): def test_domain_get(client, token, status):

View File

@ -6,6 +6,7 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
from marshmallow import fields from marshmallow import fields
from lemur.common.schema import LemurInputSchema, LemurOutputSchema from lemur.common.schema import LemurInputSchema, LemurOutputSchema
from lemur.schemas import AssociatedRoleSchema, AssociatedCertificateSchema, AssociatedAuthoritySchema from lemur.schemas import AssociatedRoleSchema, AssociatedCertificateSchema, AssociatedAuthoritySchema
@ -30,14 +31,14 @@ class UserOutputSchema(LemurOutputSchema):
authorities = fields.Nested(AssociatedAuthoritySchema, many=True) authorities = fields.Nested(AssociatedAuthoritySchema, many=True)
user_input_schema = UserInputSchema()
user_output_schema = UserOutputSchema()
users_output_schema = UserOutputSchema(many=True)
class UserNestedOutputSchema(LemurOutputSchema): class UserNestedOutputSchema(LemurOutputSchema):
__envelope__ = False __envelope__ = False
id = fields.Integer() id = fields.Integer()
username = fields.String() username = fields.String()
email = fields.Email() email = fields.Email()
active = fields.Boolean() active = fields.Boolean()
user_input_schema = UserInputSchema()
user_output_schema = UserOutputSchema()
users_output_schema = UserOutputSchema(many=True)