diff --git a/lemur/auth/permissions.py b/lemur/auth/permissions.py index 9e65d506..89da4674 100644 --- a/lemur/auth/permissions.py +++ b/lemur/auth/permissions.py @@ -15,9 +15,6 @@ from flask_principal import Permission, RoleNeed operator_permission = Permission(RoleNeed('operator')) admin_permission = Permission(RoleNeed('admin')) -CertificateCreator = namedtuple('certificate', ['method', 'value']) -CertificateCreatorNeed = partial(CertificateCreator, 'key') - CertificateOwner = namedtuple('certificate', ['method', 'value']) CertificateOwnerNeed = partial(CertificateOwner, 'role') @@ -28,8 +25,8 @@ class SensitiveDomainPermission(Permission): class CertificatePermission(Permission): - def __init__(self, certificate_id, owner, roles): - needs = [RoleNeed('admin'), CertificateCreatorNeed(certificate_id), RoleNeed(owner)] + def __init__(self, owner, roles): + needs = [RoleNeed('admin'), RoleNeed(owner), RoleNeed('creator')] for r in roles: needs.append(CertificateOwnerNeed(str(r))) diff --git a/lemur/auth/service.py b/lemur/auth/service.py index fc6a7af1..42c68b7b 100644 --- a/lemur/auth/service.py +++ b/lemur/auth/service.py @@ -27,8 +27,7 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers from lemur.users import service as user_service -from lemur.auth.permissions import CertificateCreatorNeed, \ - AuthorityCreatorNeed, RoleMemberNeed +from lemur.auth.permissions import AuthorityCreatorNeed, RoleMemberNeed def get_rsa_public_key(n, e): @@ -155,11 +154,6 @@ def on_identity_loaded(sender, identity): for authority in user.authorities: identity.provides.add(AuthorityCreatorNeed(authority.id)) - # apply ownership of certificates - if hasattr(user, 'certificates'): - for certificate in user.certificates: - identity.provides.add(CertificateCreatorNeed(certificate.id)) - g.user = user diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 01966537..7e73cbe9 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -68,19 +68,19 @@ class Certificate(db.Model): 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') - domains = relationship("Domain", secondary=certificate_associations, backref="certificate") - roles = relationship("Role", secondary=roles_certificates, backref="certificate") - replaces = relationship("Certificate", + 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') + domains = relationship('Domain', secondary=certificate_associations, backref='certificate') + roles = relationship('Role', secondary=roles_certificates, backref='certificate') + replaces = relationship('Certificate', secondary=certificate_replacement_associations, primaryjoin=id == certificate_replacement_associations.c.certificate_id, # noqa secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id, # noqa backref='replaced') - logs = relationship("Log", backref="certificate") - endpoints = relationship("Endpoint", backref='certificate') + logs = relationship('Log', backref='certificate') + endpoints = relationship('Endpoint', backref='certificate') def __init__(self, **kwargs): cert = lemur.common.utils.parse_certificate(kwargs['body']) diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index eb8d281e..d8fd4a8c 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -438,9 +438,10 @@ class CertificatePrivateKey(AuthenticatedResource): if not cert: return dict(message="Cannot find specified certificate"), 404 - if not g.current_user.is_admin: + # allow creators + if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) - permission = CertificatePermission(cert.id, owner_role, [x.name for x in cert.roles]) + permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict(message='You are not authorized to view this key'), 403 @@ -621,27 +622,32 @@ class Certificates(AuthenticatedResource): """ cert = service.get(certificate_id) - owner_role = role_service.get_by_name(cert.owner) - permission = CertificatePermission(cert.id, owner_role, [x.name for x in cert.roles]) + if not cert: + return dict(message="Cannot find specified certificate"), 404 - if permission.can(): - for destination in data['destinations']: - if destination.plugin.requires_key: - if not cert.private_key: - return dict('Unable to add destination: {0}. Certificate does not have required private key.'.format(destination.label)) + # allow creators + if g.current_user != cert.user: + owner_role = role_service.get_by_name(cert.owner) + permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) - return service.update( - certificate_id, - data['owner'], - data['description'], - data['notify'], - data['destinations'], - data['notifications'], - data['replacements'], - data['roles'] - ) + if not permission.can(): + return dict(message='You are not authorized to update this certificate'), 403 - return dict(message='You are not authorized to update this certificate'), 403 + for destination in data['destinations']: + if destination.plugin.requires_key: + if not cert.private_key: + return dict('Unable to add destination: {0}. Certificate does not have required private key.'.format(destination.label)) + + return service.update( + certificate_id, + data['owner'], + data['description'], + data['notify'], + data['destinations'], + data['notifications'], + data['replacements'], + data['roles'] + ) class NotificationCertificatesList(AuthenticatedResource): @@ -923,9 +929,10 @@ class CertificateExport(AuthenticatedResource): plugin.slug)) else: - if not g.current_user.is_admin: + # allow creators + if g.current_user != cert.user: owner_role = role_service.get_by_name(cert.owner) - permission = CertificatePermission(cert.id, owner_role, [x.name for x in cert.roles]) + permission = CertificatePermission(owner_role, [x.name for x in cert.roles]) if not permission.can(): return dict(message='You are not authorized to export this certificate.'), 403 diff --git a/lemur/logs/models.py b/lemur/logs/models.py index 7595a351..59787083 100644 --- a/lemur/logs/models.py +++ b/lemur/logs/models.py @@ -15,7 +15,7 @@ from lemur.database import db class Log(db.Model): - __tablename__ = 'log' + __tablename__ = 'logs' id = Column(Integer, primary_key=True) certificate_id = Column(Integer, ForeignKey('certificates.id')) log_type = Column(Enum('key_view', name='log_type'), nullable=False) diff --git a/lemur/migrations/versions/6d6151f5f307_.py b/lemur/migrations/versions/e3691fc396e9_.py similarity index 58% rename from lemur/migrations/versions/6d6151f5f307_.py rename to lemur/migrations/versions/e3691fc396e9_.py index 554b347c..1c5c2f15 100644 --- a/lemur/migrations/versions/6d6151f5f307_.py +++ b/lemur/migrations/versions/e3691fc396e9_.py @@ -1,28 +1,27 @@ -"""Adding private key auditing. +"""Adding logging database tables. -Revision ID: 6d6151f5f307 +Revision ID: e3691fc396e9 Revises: 932525b82f1a -Create Date: 2016-11-18 16:08:12.191959 +Create Date: 2016-11-28 13:15:46.995219 """ # revision identifiers, used by Alembic. -revision = '6d6151f5f307' +revision = 'e3691fc396e9' down_revision = '932525b82f1a' from alembic import op import sqlalchemy as sa - -from sqlalchemy_utils.types import ArrowType - +import sqlalchemy_utils def upgrade(): ### commands auto generated by Alembic - please adjust! ### - op.create_table('views', + op.create_table('logs', sa.Column('id', sa.Integer(), nullable=False), sa.Column('certificate_id', sa.Integer(), nullable=True), - sa.Column('viewed_at', ArrowType(), server_default=sa.text('now()'), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('log_type', sa.Enum('key_view', name='log_type'), nullable=False), + sa.Column('logged_at', sqlalchemy_utils.types.arrow.ArrowType(), server_default=sa.text('now()'), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), sa.ForeignKeyConstraint(['certificate_id'], ['certificates.id'], ), sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), sa.PrimaryKeyConstraint('id') @@ -32,5 +31,5 @@ def upgrade(): def downgrade(): ### commands auto generated by Alembic - please adjust! ### - op.drop_table('views') + op.drop_table('logs') ### end Alembic commands ### diff --git a/lemur/static/app/angular/logs/services.js b/lemur/static/app/angular/logs/services.js index 4ab0821b..4e6cf309 100644 --- a/lemur/static/app/angular/logs/services.js +++ b/lemur/static/app/angular/logs/services.js @@ -2,7 +2,7 @@ angular.module('lemur') .service('LogApi', function (LemurRestangular) { - return LemurRestangular.all('domains'); + return LemurRestangular.all('logs'); }) .service('LogService', function () { var LogService = this; diff --git a/lemur/static/app/angular/logs/view/view.js b/lemur/static/app/angular/logs/view/view.js index 9ec4c9e6..d57443b1 100644 --- a/lemur/static/app/angular/logs/view/view.js +++ b/lemur/static/app/angular/logs/view/view.js @@ -6,11 +6,11 @@ angular.module('lemur') $stateProvider.state('logs', { url: '/logs', templateUrl: '/angular/logs/view/view.tpl.html', - controller: 'DomainsViewController' + controller: 'LogsViewController' }); }) - .controller('DomainsViewController', function ($scope, $uibModal, DomainApi, DomainService, ngTableParams) { + .controller('LogsViewController', function ($scope, $uibModal, LogApi, LogService, ngTableParams, MomentService) { $scope.filter = {}; $scope.logsTable = new ngTableParams({ page: 1, // show first page @@ -22,10 +22,12 @@ angular.module('lemur') }, { total: 0, // length of data getData: function ($defer, params) { - DomainApi.getList(params.url()).then(function (data) { + LogApi.getList(params.url()).then(function (data) { params.total(data.total); $defer.resolve(data); }); } }); + + $scope.momentService = MomentService; }); diff --git a/lemur/static/app/angular/logs/view/view.tpl.html b/lemur/static/app/angular/logs/view/view.tpl.html index f3430f04..f8d1244b 100644 --- a/lemur/static/app/angular/logs/view/view.tpl.html +++ b/lemur/static/app/angular/logs/view/view.tpl.html @@ -12,17 +12,22 @@
- +
- +
- {{ log.logged_at }} + + + {{ momentService.createMoment(log.loggedAt) }} + + + {{ log.certificate.name }} {{ log.user.email }} - {{ log.type }} + {{ log.logType }}
diff --git a/lemur/static/app/index.html b/lemur/static/app/index.html index 9b7de523..def763b1 100644 --- a/lemur/static/app/index.html +++ b/lemur/static/app/index.html @@ -62,7 +62,7 @@
  • Roles
  • Users
  • Domains
  • -
  • Log
  • +
  • Logs
  • diff --git a/lemur/users/models.py b/lemur/users/models.py index 7c8f5f18..a92753e4 100644 --- a/lemur/users/models.py +++ b/lemur/users/models.py @@ -42,8 +42,9 @@ class User(db.Model): email = Column(String(128), unique=True) profile_picture = Column(String(255)) roles = relationship('Role', secondary=roles_users, passive_deletes=True, backref=db.backref('user'), lazy='dynamic') - certificates = relationship("Certificate", backref=db.backref('user'), lazy='dynamic') - authorities = relationship("Authority", backref=db.backref('user'), lazy='dynamic') + certificates = relationship('Certificate', backref=db.backref('user'), lazy='dynamic') + authorities = relationship('Authority', backref=db.backref('user'), lazy='dynamic') + logs = relationship('Log', backref=db.backref('user'), lazy='dynamic') def check_password(self, password): """