* Closes #147 * Fixing tests * Ensuring we can validate max dates.
This commit is contained in:
parent
bd727b825d
commit
656269ff17
@ -6,49 +6,35 @@
|
||||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import Column, Integer, String, Text, func, ForeignKey, DateTime, PassiveDefault, Boolean
|
||||
from sqlalchemy.dialects.postgresql import JSON
|
||||
|
||||
from lemur.database import db
|
||||
from lemur.models import roles_authorities
|
||||
from lemur.common import defaults
|
||||
|
||||
|
||||
class Authority(db.Model):
|
||||
__tablename__ = 'authorities'
|
||||
id = Column(Integer, primary_key=True)
|
||||
owner = Column(String(128))
|
||||
owner = Column(String(128), nullable=False)
|
||||
name = Column(String(128), unique=True)
|
||||
body = 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)
|
||||
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
|
||||
plugin_name = Column(String(64))
|
||||
description = Column(Text)
|
||||
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')
|
||||
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):
|
||||
cert = x509.load_pem_x509_certificate(bytes(body), default_backend())
|
||||
self.name = name
|
||||
self.body = body
|
||||
self.chain = chain
|
||||
self.owner = owner
|
||||
self.description = description
|
||||
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
|
||||
def __init__(self, **kwargs):
|
||||
self.owner = kwargs['owner']
|
||||
self.roles = kwargs.get('roles', [])
|
||||
self.name = kwargs.get('name')
|
||||
self.description = kwargs.get('description')
|
||||
self.authority_certificate = kwargs['authority_certificate']
|
||||
self.plugin_name = kwargs['plugin']['slug']
|
||||
|
@ -12,6 +12,7 @@ from marshmallow import validate
|
||||
from marshmallow.exceptions import ValidationError
|
||||
|
||||
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 import validators
|
||||
|
||||
@ -61,25 +62,39 @@ class AuthorityInputSchema(LemurInputSchema):
|
||||
|
||||
|
||||
class AuthorityUpdateSchema(LemurInputSchema):
|
||||
owner = fields.Email()
|
||||
owner = fields.Email(required=True)
|
||||
description = fields.String()
|
||||
active = fields.Boolean()
|
||||
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):
|
||||
id = fields.Integer()
|
||||
description = fields.String()
|
||||
name = fields.String()
|
||||
owner = fields.Email()
|
||||
not_before = fields.DateTime()
|
||||
not_after = fields.DateTime()
|
||||
plugin = fields.Nested(PluginOutputSchema)
|
||||
body = fields.String()
|
||||
chain = fields.String()
|
||||
active = fields.Boolean()
|
||||
options = fields.Dict()
|
||||
roles = fields.List(fields.Nested(AssociatedRoleSchema))
|
||||
authority_certificate = fields.Nested(RootAuthorityCertificateOutputSchema)
|
||||
|
||||
|
||||
class AuthorityNestedOutputSchema(LemurOutputSchema):
|
||||
@ -87,13 +102,8 @@ class AuthorityNestedOutputSchema(LemurOutputSchema):
|
||||
description = fields.String()
|
||||
name = fields.String()
|
||||
owner = fields.Email()
|
||||
not_before = fields.DateTime()
|
||||
not_after = fields.DateTime()
|
||||
plugin = fields.Nested(PluginOutputSchema)
|
||||
body = fields.String()
|
||||
chain = fields.String()
|
||||
active = fields.Boolean()
|
||||
options = fields.Dict()
|
||||
|
||||
|
||||
authority_update_schema = AuthorityUpdateSchema()
|
||||
|
@ -9,14 +9,13 @@
|
||||
|
||||
"""
|
||||
from flask import g
|
||||
from flask import current_app
|
||||
|
||||
from lemur import database
|
||||
from lemur.extensions import metrics
|
||||
from lemur.authorities.models import Authority
|
||||
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):
|
||||
@ -40,42 +39,28 @@ def update(authority_id, description=None, owner=None, active=None, roles=None):
|
||||
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:
|
||||
"""
|
||||
|
||||
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 = []
|
||||
for r in issuer_roles:
|
||||
for r in roles:
|
||||
role = role_service.create(
|
||||
r['name'],
|
||||
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'])
|
||||
|
||||
# the user creating the authority should be able to administer it
|
||||
@ -93,22 +78,39 @@ def create(kwargs):
|
||||
)
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
|
@ -167,7 +167,7 @@ class AuthoritiesList(AuthenticatedResource):
|
||||
:statuscode 403: unauthenticated
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
return service.create(data)
|
||||
return service.create(**data)
|
||||
|
||||
|
||||
class Authorities(AuthenticatedResource):
|
||||
|
@ -36,7 +36,7 @@ class Certificate(db.Model):
|
||||
__tablename__ = 'certificates'
|
||||
id = Column(Integer, primary_key=True)
|
||||
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))
|
||||
active = Column(Boolean, default=True)
|
||||
|
||||
@ -59,7 +59,9 @@ class Certificate(db.Model):
|
||||
san = Column(String(1024)) # TODO this should be migrated to boolean
|
||||
|
||||
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')
|
||||
destinations = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
|
||||
sources = relationship("Source", secondary=certificate_source_associations, backref='certificate')
|
||||
|
@ -6,7 +6,6 @@
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from flask import current_app
|
||||
|
||||
from marshmallow import fields, validates_schema, post_load
|
||||
from marshmallow.exceptions import ValidationError
|
||||
|
||||
@ -17,7 +16,7 @@ from lemur.authorities.schemas import AuthorityNestedOutputSchema
|
||||
from lemur.destinations.schemas import DestinationNestedOutputSchema
|
||||
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.common.schema import LemurInputSchema, LemurOutputSchema
|
||||
@ -115,7 +114,7 @@ class CertificateOutputSchema(LemurOutputSchema):
|
||||
signing_algorithm = fields.String()
|
||||
status = fields.Boolean()
|
||||
user = fields.Nested(UserNestedOutputSchema)
|
||||
# domains = fields.Nested(DomainNestedOutputSchema)
|
||||
domains = fields.Nested(DomainNestedOutputSchema)
|
||||
destinations = fields.Nested(DestinationNestedOutputSchema, many=True)
|
||||
notifications = fields.Nested(NotificationNestedOutputSchema, many=True)
|
||||
replaces = fields.Nested(CertificateNestedOutputSchema, many=True)
|
||||
|
@ -120,7 +120,6 @@ def mint(**kwargs):
|
||||
Minting is slightly different for each authority.
|
||||
Support for multiple authorities is handled by individual plugins.
|
||||
|
||||
:param issuer_options:
|
||||
"""
|
||||
authority = kwargs['authority']
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
"""
|
||||
from functools import wraps
|
||||
from flask import request
|
||||
from flask import request, current_app
|
||||
|
||||
from sqlalchemy.orm.collections import InstrumentedList
|
||||
|
||||
@ -135,6 +135,7 @@ def validate_schema(input_schema, output_schema):
|
||||
try:
|
||||
resp = f(*args, **kwargs)
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return dict(message=e.message), 500
|
||||
|
||||
if isinstance(resp, tuple):
|
||||
|
@ -102,19 +102,19 @@ def dates(data):
|
||||
raise ValidationError('Validity start must be before validity end.')
|
||||
|
||||
if data.get('authority'):
|
||||
if data.get('validity_start').replace(tzinfo=None) < data['authority'].not_before:
|
||||
raise ValidationError('Validity start must not be before {0}'.format(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'].authority_certificate.not_before))
|
||||
|
||||
if data.get('validity_end').replace(tzinfo=None) > data['authority'].not_after:
|
||||
raise ValidationError('Validity end must not be after {0}'.format(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'].authority_certificate.not_after))
|
||||
|
||||
if data.get('validity_years'):
|
||||
now = arrow.utcnow()
|
||||
end = now.replace(years=+data['validity_years'])
|
||||
|
||||
if data.get('authority'):
|
||||
if now.naive < data['authority'].not_before:
|
||||
raise ValidationError('Validity start must not be before {0}'.format(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'].authority_certificate.not_before))
|
||||
|
||||
if end.naive > data['authority'].not_after:
|
||||
raise ValidationError('Validity end must not be after {0}'.format(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'].authority_certificate.not_after))
|
||||
|
@ -9,7 +9,7 @@ from marshmallow import fields
|
||||
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
||||
from lemur.schemas import AssociatedCertificateSchema
|
||||
|
||||
from lemur.certificates.schemas import CertificateNestedOutputSchema
|
||||
# from lemur.certificates.schemas import CertificateNestedOutputSchema
|
||||
|
||||
|
||||
class DomainInputSchema(LemurInputSchema):
|
||||
@ -23,7 +23,7 @@ class DomainOutputSchema(LemurOutputSchema):
|
||||
id = fields.Integer()
|
||||
name = fields.String()
|
||||
sensitive = fields.Boolean()
|
||||
certificates = fields.Nested(CertificateNestedOutputSchema, many=True, missing=[])
|
||||
# certificates = fields.Nested(CertificateNestedOutputSchema, many=True, missing=[])
|
||||
|
||||
|
||||
class DomainNestedOutputSchema(DomainOutputSchema):
|
||||
|
58
lemur/migrations/versions/3307381f3b88_.py
Normal file
58
lemur/migrations/versions/3307381f3b88_.py
Normal 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 ###
|
@ -1,4 +1,4 @@
|
||||
"""empty message
|
||||
"""
|
||||
|
||||
Revision ID: 412b22cb656a
|
||||
Revises: 4c50b903d1ae
|
||||
|
@ -9,17 +9,15 @@
|
||||
"""
|
||||
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.destinations.models import Destination
|
||||
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.schema import LemurSchema, LemurInputSchema, LemurOutputSchema
|
||||
|
||||
from lemur.destinations.models import Destination
|
||||
from lemur.notifications.models import Notification
|
||||
from lemur.plugins import plugins
|
||||
from lemur.roles.models import Role
|
||||
from lemur.users.models import User
|
||||
|
||||
|
||||
class AssociatedAuthoritySchema(LemurInputSchema):
|
||||
|
@ -93,8 +93,8 @@
|
||||
is-open="popup1.opened"
|
||||
datepicker-options="dateOptions"
|
||||
close-text="Close"
|
||||
max-date="authority.authority.notAfter"
|
||||
min-date="authority.authority.notBefore"
|
||||
max-date="authority.authority.authorityCertificate.notAfter"
|
||||
min-date="authority.authority.authorityCertificate.notBefore"
|
||||
alt-input-formats="altInputFormats" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
|
@ -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.authoritiesTable = new ngTableParams({
|
||||
page: 1, // show first page
|
||||
@ -35,6 +35,8 @@ angular.module('lemur')
|
||||
}
|
||||
});
|
||||
|
||||
$scope.momentService = MomentService;
|
||||
|
||||
$scope.updateActive = function (authority) {
|
||||
AuthorityService.updateActive(authority).then(
|
||||
function () {
|
||||
|
@ -1,51 +1,120 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2 class="featurette-heading">Authorities
|
||||
<span class="text-muted"><small>The nail that sticks out farthest gets hammered the hardest</small></span></h2>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="btn-group pull-right">
|
||||
<button class="btn btn-primary" ng-click="create()">Create</button>
|
||||
<div class="col-md-12">
|
||||
<h2 class="featurette-heading">Authorities
|
||||
<span class="text-muted"><small>The nail that sticks out farthest gets hammered the hardest</small></span>
|
||||
</h2>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<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 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>
|
||||
|
@ -102,8 +102,8 @@
|
||||
is-open="popup1.opened"
|
||||
datepicker-options="dateOptions"
|
||||
close-text="Close"
|
||||
max-date="certificate.authority.notAfter"
|
||||
min-date="certificate.authority.notBefore"
|
||||
max-date="certificate.authority.authorityCertificate.notAfter"
|
||||
min-date="certificate.authority.authorityCertificate.notBefore"
|
||||
alt-input-formats="altInputFormats"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open1()"><i
|
||||
@ -121,8 +121,8 @@
|
||||
is-open="popup2.opened"
|
||||
datepicker-options="dateOptions"
|
||||
close-text="Close"
|
||||
max-date="certificate.authority.notAfter"
|
||||
min-date="certificate.authority.notBefore"
|
||||
max-date="certificate.authority.authorityCertificate.notAfter"
|
||||
min-date="certificate.authority.authorityCertificate.notBefore"
|
||||
alt-input-formats="altInputFormats"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open2()"><i
|
||||
|
@ -137,6 +137,7 @@ def issuer_plugin():
|
||||
from lemur.plugins.base import register
|
||||
from .plugins.issuer_plugin import TestIssuerPlugin
|
||||
register(TestIssuerPlugin)
|
||||
return TestIssuerPlugin
|
||||
|
||||
|
||||
@pytest.yield_fixture(scope="function")
|
||||
@ -147,7 +148,7 @@ def logged_in_user(app):
|
||||
|
||||
|
||||
@pytest.yield_fixture(scope="function")
|
||||
def logged_in_admin(app):
|
||||
def logged_in_admin(session, app):
|
||||
with app.test_request_context():
|
||||
identity_changed.send(current_app._get_current_object(), identity=Identity(2))
|
||||
yield
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
from datetime import date
|
||||
|
||||
from factory import Sequence, post_generation
|
||||
from factory import Sequence, post_generation, SubFactory
|
||||
from factory.alchemy import SQLAlchemyModelFactory
|
||||
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.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):
|
||||
@ -26,27 +26,6 @@ class BaseFactory(SQLAlchemyModelFactory):
|
||||
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):
|
||||
"""Certificate factory."""
|
||||
name = Sequence(lambda n: 'certificate{0}'.format(n))
|
||||
@ -135,6 +114,27 @@ class CertificateFactory(BaseFactory):
|
||||
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):
|
||||
"""Destination factory."""
|
||||
plugin_name = Sequence(lambda n: 'destination{0}'.format(n))
|
||||
|
@ -25,14 +25,6 @@ def test_authority_input_schema(client, role):
|
||||
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):
|
||||
assert client.get(api.url_for(AuthoritiesList), headers=user['token']).json['total'] == 0
|
||||
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
|
||||
|
||||
|
||||
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", [
|
||||
(VALID_USER_HEADER_TOKEN, 404),
|
||||
(VALID_ADMIN_HEADER_TOKEN, 404),
|
||||
(VALID_USER_HEADER_TOKEN, 200),
|
||||
(VALID_ADMIN_HEADER_TOKEN, 200),
|
||||
('', 401)
|
||||
])
|
||||
def test_authority_get(client, token, status):
|
||||
@ -64,8 +70,8 @@ def test_authority_post(client, token, status):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("token,status", [
|
||||
(VALID_USER_HEADER_TOKEN, 404),
|
||||
(VALID_ADMIN_HEADER_TOKEN, 404),
|
||||
(VALID_USER_HEADER_TOKEN, 400),
|
||||
(VALID_ADMIN_HEADER_TOKEN, 400),
|
||||
('', 401)
|
||||
])
|
||||
def test_authority_put(client, token, status):
|
||||
|
@ -7,8 +7,8 @@ from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN
|
||||
|
||||
|
||||
@pytest.mark.parametrize("token,status", [
|
||||
(VALID_USER_HEADER_TOKEN, 404),
|
||||
(VALID_ADMIN_HEADER_TOKEN, 404),
|
||||
(VALID_USER_HEADER_TOKEN, 200),
|
||||
(VALID_ADMIN_HEADER_TOKEN, 200),
|
||||
('', 401)
|
||||
])
|
||||
def test_domain_get(client, token, status):
|
||||
|
@ -6,6 +6,7 @@
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
from marshmallow import fields
|
||||
|
||||
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
||||
from lemur.schemas import AssociatedRoleSchema, AssociatedCertificateSchema, AssociatedAuthoritySchema
|
||||
|
||||
@ -30,14 +31,14 @@ class UserOutputSchema(LemurOutputSchema):
|
||||
authorities = fields.Nested(AssociatedAuthoritySchema, many=True)
|
||||
|
||||
|
||||
user_input_schema = UserInputSchema()
|
||||
user_output_schema = UserOutputSchema()
|
||||
users_output_schema = UserOutputSchema(many=True)
|
||||
|
||||
|
||||
class UserNestedOutputSchema(LemurOutputSchema):
|
||||
__envelope__ = False
|
||||
id = fields.Integer()
|
||||
username = fields.String()
|
||||
email = fields.Email()
|
||||
active = fields.Boolean()
|
||||
|
||||
|
||||
user_input_schema = UserInputSchema()
|
||||
user_output_schema = UserOutputSchema()
|
||||
users_output_schema = UserOutputSchema(many=True)
|
||||
|
Loading…
Reference in New Issue
Block a user