From 656269ff17778dd7b5acf42aebfce9aadd800384 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Mon, 23 May 2016 11:28:25 -0700 Subject: [PATCH] Closes #147 (#328) * Closes #147 * Fixing tests * Ensuring we can validate max dates. --- lemur/authorities/models.py | 36 ++-- lemur/authorities/schemas.py | 30 ++-- lemur/authorities/service.py | 88 +++++----- lemur/authorities/views.py | 2 +- lemur/certificates/models.py | 6 +- lemur/certificates/schemas.py | 5 +- lemur/certificates/service.py | 1 - lemur/common/schema.py | 3 +- lemur/common/validators.py | 16 +- lemur/domains/schemas.py | 4 +- lemur/migrations/versions/3307381f3b88_.py | 58 +++++++ lemur/migrations/versions/412b22cb656a_.py | 2 +- lemur/schemas.py | 10 +- .../authorities/authority/tracking.tpl.html | 4 +- .../app/angular/authorities/view/view.js | 4 +- .../angular/authorities/view/view.tpl.html | 163 +++++++++++++----- .../certificate/tracking.tpl.html | 8 +- lemur/tests/conftest.py | 3 +- lemur/tests/factories.py | 46 ++--- lemur/tests/test_authorities.py | 30 ++-- lemur/tests/test_domains.py | 4 +- lemur/users/schemas.py | 11 +- 22 files changed, 334 insertions(+), 200 deletions(-) create mode 100644 lemur/migrations/versions/3307381f3b88_.py diff --git a/lemur/authorities/models.py b/lemur/authorities/models.py index f9b93c35..1777d7dd 100644 --- a/lemur/authorities/models.py +++ b/lemur/authorities/models.py @@ -6,49 +6,35 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -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'] diff --git a/lemur/authorities/schemas.py b/lemur/authorities/schemas.py index e065c8f5..b6b18f75 100644 --- a/lemur/authorities/schemas.py +++ b/lemur/authorities/schemas.py @@ -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() diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index a6066ab8..2c9b4d3f 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -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 diff --git a/lemur/authorities/views.py b/lemur/authorities/views.py index cd204d8d..921feec8 100644 --- a/lemur/authorities/views.py +++ b/lemur/authorities/views.py @@ -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): diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index ae9191cb..b4f7d8a8 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -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') diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py index a31a1c51..8f112269 100644 --- a/lemur/certificates/schemas.py +++ b/lemur/certificates/schemas.py @@ -6,7 +6,6 @@ .. moduleauthor:: Kevin Glisson """ 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) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index c4f0822b..54808a94 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -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'] diff --git a/lemur/common/schema.py b/lemur/common/schema.py index e2e4ee82..a46ccc70 100644 --- a/lemur/common/schema.py +++ b/lemur/common/schema.py @@ -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): diff --git a/lemur/common/validators.py b/lemur/common/validators.py index 99ae5c19..395a7c2a 100644 --- a/lemur/common/validators.py +++ b/lemur/common/validators.py @@ -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)) diff --git a/lemur/domains/schemas.py b/lemur/domains/schemas.py index 5b1b10dd..a89d8248 100644 --- a/lemur/domains/schemas.py +++ b/lemur/domains/schemas.py @@ -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): diff --git a/lemur/migrations/versions/3307381f3b88_.py b/lemur/migrations/versions/3307381f3b88_.py new file mode 100644 index 00000000..e742be38 --- /dev/null +++ b/lemur/migrations/versions/3307381f3b88_.py @@ -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 ### diff --git a/lemur/migrations/versions/412b22cb656a_.py b/lemur/migrations/versions/412b22cb656a_.py index aaff03af..06122f7d 100644 --- a/lemur/migrations/versions/412b22cb656a_.py +++ b/lemur/migrations/versions/412b22cb656a_.py @@ -1,4 +1,4 @@ -"""empty message +""" Revision ID: 412b22cb656a Revises: 4c50b903d1ae diff --git a/lemur/schemas.py b/lemur/schemas.py index f86eb3ec..24848d42 100644 --- a/lemur/schemas.py +++ b/lemur/schemas.py @@ -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): diff --git a/lemur/static/app/angular/authorities/authority/tracking.tpl.html b/lemur/static/app/angular/authorities/authority/tracking.tpl.html index 1b766fe5..d6361d5f 100644 --- a/lemur/static/app/angular/authorities/authority/tracking.tpl.html +++ b/lemur/static/app/angular/authorities/authority/tracking.tpl.html @@ -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" /> diff --git a/lemur/static/app/angular/authorities/view/view.js b/lemur/static/app/angular/authorities/view/view.js index 802779e0..1a2c9357 100644 --- a/lemur/static/app/angular/authorities/view/view.js +++ b/lemur/static/app/angular/authorities/view/view.js @@ -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 () { diff --git a/lemur/static/app/angular/authorities/view/view.tpl.html b/lemur/static/app/angular/authorities/view/view.tpl.html index c417f647..a4b4cd30 100644 --- a/lemur/static/app/angular/authorities/view/view.tpl.html +++ b/lemur/static/app/angular/authorities/view/view.tpl.html @@ -1,51 +1,120 @@
-
-

Authorities - The nail that sticks out farthest gets hammered the hardest

-
-
-
- +
+

Authorities + The nail that sticks out farthest gets hammered the hardest +

+
+
+
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + +
+
    +
  • {{ authority.name }}
  • +
  • {{ authority.owner }}
  • +
+
+
+ +
+
+ {{ authority.authorityCertificate.cn }} + +
+ Permalink + + +
+
+ + + Basic Info +
    +
  • + Creator + + {{ authority.authorityCertificate.user.email }} + +
  • +
  • + Not Before + + {{ momentService.createMoment(authority.authorityCertificate.notBefore) }} + +
  • +
  • + Not After + + {{ momentService.createMoment(authority.authorityCertificate.notAfter) }} + +
  • +
  • + Description +

    {{ authority.description }}

    +
  • +
+
+ + Roles +
    +
  • + {{ role.name }} + {{ role.description }} +
  • +
+
+
+ + + + Chain + + +
{{ authority.authorityCertificate.chain }}
+
+ + + Public Certificate + + +
{{ authority.authorityCertificate.body }}
+
+
+
+
-
- -
-
-
-
- - - - - - - - - -
-
    -
  • {{ authority.name }}
  • -
  • {{ authority.description }}
  • -
-
-
- -
-
- - -
- Permalink - -
-
-
-
diff --git a/lemur/static/app/angular/certificates/certificate/tracking.tpl.html b/lemur/static/app/angular/certificates/certificate/tracking.tpl.html index 31bb8921..5ed77615 100644 --- a/lemur/static/app/angular/certificates/certificate/tracking.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/tracking.tpl.html @@ -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"/>