Log fixes (#534)

* tying up some loose ends with event logging

* Ensuring creators can access
This commit is contained in:
kevgliss 2016-11-28 14:13:16 -08:00 committed by GitHub
parent e2143d3ee8
commit 727bc87ede
11 changed files with 70 additions and 65 deletions

View File

@ -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)))

View File

@ -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

View File

@ -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'])

View File

@ -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

View File

@ -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)

View File

@ -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 ###

View File

@ -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;

View File

@ -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;
});

View File

@ -12,17 +12,22 @@
<div class="clearfix"></div>
</div>
<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>
<tr ng-repeat="log in $data track by $index">
<td data-title="'Logged At'" sortable="'logged_at'" filter="{ 'logged_at': 'text' }">
{{ log.logged_at }}
<td data-title="'Logged'" sortable="'logged_at'" filter="{ 'logged_at': 'text' }">
<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 data-title="'User'" sortable="'user'" filter="{ 'user.email': 'text' }">
{{ log.user.email }}
</td>
<td data-title="'Type'" sortable="'type'" filter="{ 'type': 'text' }">
{{ log.type }}
{{ log.logType }}
</td>
</tbody>
</table>

View File

@ -62,7 +62,7 @@
<li><a ui-sref="roles">Roles</a></li>
<li><a ui-sref="users">Users</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>
</li>
</ul>

View File

@ -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):
"""