Log fixes (#534)
* tying up some loose ends with event logging * Ensuring creators can access
This commit is contained in:
parent
e2143d3ee8
commit
727bc87ede
|
@ -15,9 +15,6 @@ from flask_principal import Permission, RoleNeed
|
||||||
operator_permission = Permission(RoleNeed('operator'))
|
operator_permission = Permission(RoleNeed('operator'))
|
||||||
admin_permission = Permission(RoleNeed('admin'))
|
admin_permission = Permission(RoleNeed('admin'))
|
||||||
|
|
||||||
CertificateCreator = namedtuple('certificate', ['method', 'value'])
|
|
||||||
CertificateCreatorNeed = partial(CertificateCreator, 'key')
|
|
||||||
|
|
||||||
CertificateOwner = namedtuple('certificate', ['method', 'value'])
|
CertificateOwner = namedtuple('certificate', ['method', 'value'])
|
||||||
CertificateOwnerNeed = partial(CertificateOwner, 'role')
|
CertificateOwnerNeed = partial(CertificateOwner, 'role')
|
||||||
|
|
||||||
|
@ -28,8 +25,8 @@ class SensitiveDomainPermission(Permission):
|
||||||
|
|
||||||
|
|
||||||
class CertificatePermission(Permission):
|
class CertificatePermission(Permission):
|
||||||
def __init__(self, certificate_id, owner, roles):
|
def __init__(self, owner, roles):
|
||||||
needs = [RoleNeed('admin'), CertificateCreatorNeed(certificate_id), RoleNeed(owner)]
|
needs = [RoleNeed('admin'), RoleNeed(owner), RoleNeed('creator')]
|
||||||
for r in roles:
|
for r in roles:
|
||||||
needs.append(CertificateOwnerNeed(str(r)))
|
needs.append(CertificateOwnerNeed(str(r)))
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,7 @@ from cryptography.hazmat.primitives import serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
|
||||||
|
|
||||||
from lemur.users import service as user_service
|
from lemur.users import service as user_service
|
||||||
from lemur.auth.permissions import CertificateCreatorNeed, \
|
from lemur.auth.permissions import AuthorityCreatorNeed, RoleMemberNeed
|
||||||
AuthorityCreatorNeed, RoleMemberNeed
|
|
||||||
|
|
||||||
|
|
||||||
def get_rsa_public_key(n, e):
|
def get_rsa_public_key(n, e):
|
||||||
|
@ -155,11 +154,6 @@ def on_identity_loaded(sender, identity):
|
||||||
for authority in user.authorities:
|
for authority in user.authorities:
|
||||||
identity.provides.add(AuthorityCreatorNeed(authority.id))
|
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
|
g.user = user
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -68,19 +68,19 @@ class Certificate(db.Model):
|
||||||
authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE"))
|
authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE"))
|
||||||
root_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')
|
||||||
domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
|
domains = relationship('Domain', secondary=certificate_associations, backref='certificate')
|
||||||
roles = relationship("Role", secondary=roles_certificates, backref="certificate")
|
roles = relationship('Role', secondary=roles_certificates, backref='certificate')
|
||||||
replaces = relationship("Certificate",
|
replaces = relationship('Certificate',
|
||||||
secondary=certificate_replacement_associations,
|
secondary=certificate_replacement_associations,
|
||||||
primaryjoin=id == certificate_replacement_associations.c.certificate_id, # noqa
|
primaryjoin=id == certificate_replacement_associations.c.certificate_id, # noqa
|
||||||
secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id, # noqa
|
secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id, # noqa
|
||||||
backref='replaced')
|
backref='replaced')
|
||||||
|
|
||||||
logs = relationship("Log", backref="certificate")
|
logs = relationship('Log', backref='certificate')
|
||||||
endpoints = relationship("Endpoint", backref='certificate')
|
endpoints = relationship('Endpoint', backref='certificate')
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
cert = lemur.common.utils.parse_certificate(kwargs['body'])
|
cert = lemur.common.utils.parse_certificate(kwargs['body'])
|
||||||
|
|
|
@ -438,9 +438,10 @@ class CertificatePrivateKey(AuthenticatedResource):
|
||||||
if not cert:
|
if not cert:
|
||||||
return dict(message="Cannot find specified certificate"), 404
|
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)
|
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():
|
if not permission.can():
|
||||||
return dict(message='You are not authorized to view this key'), 403
|
return dict(message='You are not authorized to view this key'), 403
|
||||||
|
@ -621,27 +622,32 @@ class Certificates(AuthenticatedResource):
|
||||||
"""
|
"""
|
||||||
cert = service.get(certificate_id)
|
cert = service.get(certificate_id)
|
||||||
|
|
||||||
owner_role = role_service.get_by_name(cert.owner)
|
if not cert:
|
||||||
permission = CertificatePermission(cert.id, owner_role, [x.name for x in cert.roles])
|
return dict(message="Cannot find specified certificate"), 404
|
||||||
|
|
||||||
if permission.can():
|
# allow creators
|
||||||
for destination in data['destinations']:
|
if g.current_user != cert.user:
|
||||||
if destination.plugin.requires_key:
|
owner_role = role_service.get_by_name(cert.owner)
|
||||||
if not cert.private_key:
|
permission = CertificatePermission(owner_role, [x.name for x in cert.roles])
|
||||||
return dict('Unable to add destination: {0}. Certificate does not have required private key.'.format(destination.label))
|
|
||||||
|
|
||||||
return service.update(
|
if not permission.can():
|
||||||
certificate_id,
|
return dict(message='You are not authorized to update this certificate'), 403
|
||||||
data['owner'],
|
|
||||||
data['description'],
|
|
||||||
data['notify'],
|
|
||||||
data['destinations'],
|
|
||||||
data['notifications'],
|
|
||||||
data['replacements'],
|
|
||||||
data['roles']
|
|
||||||
)
|
|
||||||
|
|
||||||
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):
|
class NotificationCertificatesList(AuthenticatedResource):
|
||||||
|
@ -923,9 +929,10 @@ class CertificateExport(AuthenticatedResource):
|
||||||
plugin.slug))
|
plugin.slug))
|
||||||
|
|
||||||
else:
|
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)
|
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():
|
if not permission.can():
|
||||||
return dict(message='You are not authorized to export this certificate.'), 403
|
return dict(message='You are not authorized to export this certificate.'), 403
|
||||||
|
|
|
@ -15,7 +15,7 @@ from lemur.database import db
|
||||||
|
|
||||||
|
|
||||||
class Log(db.Model):
|
class Log(db.Model):
|
||||||
__tablename__ = 'log'
|
__tablename__ = 'logs'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
certificate_id = Column(Integer, ForeignKey('certificates.id'))
|
certificate_id = Column(Integer, ForeignKey('certificates.id'))
|
||||||
log_type = Column(Enum('key_view', name='log_type'), nullable=False)
|
log_type = Column(Enum('key_view', name='log_type'), nullable=False)
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
"""Adding private key auditing.
|
"""Adding logging database tables.
|
||||||
|
|
||||||
Revision ID: 6d6151f5f307
|
Revision ID: e3691fc396e9
|
||||||
Revises: 932525b82f1a
|
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 identifiers, used by Alembic.
|
||||||
revision = '6d6151f5f307'
|
revision = 'e3691fc396e9'
|
||||||
down_revision = '932525b82f1a'
|
down_revision = '932525b82f1a'
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
import sqlalchemy_utils
|
||||||
from sqlalchemy_utils.types import ArrowType
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
### commands auto generated by Alembic - please adjust! ###
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('views',
|
op.create_table('logs',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('certificate_id', sa.Integer(), nullable=True),
|
sa.Column('certificate_id', sa.Integer(), nullable=True),
|
||||||
sa.Column('viewed_at', ArrowType(), server_default=sa.text('now()'), nullable=False),
|
sa.Column('log_type', sa.Enum('key_view', name='log_type'), nullable=False),
|
||||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
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(['certificate_id'], ['certificates.id'], ),
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
@ -32,5 +31,5 @@ def upgrade():
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
### commands auto generated by Alembic - please adjust! ###
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
op.drop_table('views')
|
op.drop_table('logs')
|
||||||
### end Alembic commands ###
|
### end Alembic commands ###
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
angular.module('lemur')
|
angular.module('lemur')
|
||||||
.service('LogApi', function (LemurRestangular) {
|
.service('LogApi', function (LemurRestangular) {
|
||||||
return LemurRestangular.all('domains');
|
return LemurRestangular.all('logs');
|
||||||
})
|
})
|
||||||
.service('LogService', function () {
|
.service('LogService', function () {
|
||||||
var LogService = this;
|
var LogService = this;
|
||||||
|
|
|
@ -6,11 +6,11 @@ angular.module('lemur')
|
||||||
$stateProvider.state('logs', {
|
$stateProvider.state('logs', {
|
||||||
url: '/logs',
|
url: '/logs',
|
||||||
templateUrl: '/angular/logs/view/view.tpl.html',
|
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.filter = {};
|
||||||
$scope.logsTable = new ngTableParams({
|
$scope.logsTable = new ngTableParams({
|
||||||
page: 1, // show first page
|
page: 1, // show first page
|
||||||
|
@ -22,10 +22,12 @@ angular.module('lemur')
|
||||||
}, {
|
}, {
|
||||||
total: 0, // length of data
|
total: 0, // length of data
|
||||||
getData: function ($defer, params) {
|
getData: function ($defer, params) {
|
||||||
DomainApi.getList(params.url()).then(function (data) {
|
LogApi.getList(params.url()).then(function (data) {
|
||||||
params.total(data.total);
|
params.total(data.total);
|
||||||
$defer.resolve(data);
|
$defer.resolve(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$scope.momentService = MomentService;
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,17 +12,22 @@
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table ng-table="domainsTable" class="table table-striped" show-filter="showFilter" template-pagination="angular/pager.html">
|
<table ng-table="logsTable" class="table table-striped" show-filter="showFilter" template-pagination="angular/pager.html">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="log in $data track by $index">
|
<tr ng-repeat="log in $data track by $index">
|
||||||
<td data-title="'Logged At'" sortable="'logged_at'" filter="{ 'logged_at': 'text' }">
|
<td data-title="'Logged'" sortable="'logged_at'" filter="{ 'logged_at': 'text' }">
|
||||||
{{ log.logged_at }}
|
<span class="pull-right" uib-tooltip="{{ log.loggedAt }}">
|
||||||
|
{{ momentService.createMoment(log.loggedAt) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td data-title="'Certificate'" sortable="'certificate'" filter="{ 'certificate.name': 'text' }">
|
||||||
|
{{ log.certificate.name }}
|
||||||
</td>
|
</td>
|
||||||
<td data-title="'User'" sortable="'user'" filter="{ 'user.email': 'text' }">
|
<td data-title="'User'" sortable="'user'" filter="{ 'user.email': 'text' }">
|
||||||
{{ log.user.email }}
|
{{ log.user.email }}
|
||||||
</td>
|
</td>
|
||||||
<td data-title="'Type'" sortable="'type'" filter="{ 'type': 'text' }">
|
<td data-title="'Type'" sortable="'type'" filter="{ 'type': 'text' }">
|
||||||
{{ log.type }}
|
{{ log.logType }}
|
||||||
</td>
|
</td>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
<li><a ui-sref="roles">Roles</a></li>
|
<li><a ui-sref="roles">Roles</a></li>
|
||||||
<li><a ui-sref="users">Users</a></li>
|
<li><a ui-sref="users">Users</a></li>
|
||||||
<li><a ui-sref="domains">Domains</a></li>
|
<li><a ui-sref="domains">Domains</a></li>
|
||||||
<li><a ui-sref="log">Log</a></li>
|
<li><a ui-sref="logs">Logs</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -42,8 +42,9 @@ class User(db.Model):
|
||||||
email = Column(String(128), unique=True)
|
email = Column(String(128), unique=True)
|
||||||
profile_picture = Column(String(255))
|
profile_picture = Column(String(255))
|
||||||
roles = relationship('Role', secondary=roles_users, passive_deletes=True, backref=db.backref('user'), lazy='dynamic')
|
roles = relationship('Role', secondary=roles_users, passive_deletes=True, backref=db.backref('user'), lazy='dynamic')
|
||||||
certificates = relationship("Certificate", backref=db.backref('user'), lazy='dynamic')
|
certificates = relationship('Certificate', backref=db.backref('user'), lazy='dynamic')
|
||||||
authorities = relationship("Authority", 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):
|
def check_password(self, password):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue