Add application_id column to AuditEvent

Use application_id and portfolio_id if the resource is a portfolio in AuditableMixin
Clean up some residual references to workspace
This commit is contained in:
leigh-mil 2019-05-15 18:14:26 -04:00
parent 42900a20a6
commit b3ecd1658c
5 changed files with 83 additions and 11 deletions

View File

@ -0,0 +1,32 @@
"""add_appliction_id_to_auditevent
Revision ID: c5deba1826be
Revises: 404bb5bb3a0e
Create Date: 2019-05-15 16:30:27.981456
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'c5deba1826be'
down_revision = '404bb5bb3a0e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('audit_events', sa.Column('application_id', postgresql.UUID(as_uuid=True), nullable=True))
op.create_index(op.f('ix_audit_events_application_id'), 'audit_events', ['application_id'], unique=False)
op.create_foreign_key('audit_events_application_application_id', 'audit_events', 'applications', ['application_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('audit_events_application_application_id', 'audit_events', type_='foreignkey')
op.drop_index(op.f('ix_audit_events_application_id'), table_name='audit_events')
op.drop_column('audit_events', 'application_id')
# ### end Alembic commands ###

View File

@ -14,15 +14,10 @@ class AuditEventQuery(Query):
return cls.paginate(query, pagination_opts)
@classmethod
def get_ws_events(cls, portfolio_id, pagination_opts):
def get_portfolio_events(cls, portfolio_id, pagination_opts):
query = (
db.session.query(cls.model)
.filter(
or_(
cls.model.portfolio_id == portfolio_id,
cls.model.resource_id == portfolio_id,
)
)
.filter(cls.model.portfolio_id == portfolio_id)
.order_by(cls.model.time_created.desc())
)
return cls.paginate(query, pagination_opts)
@ -39,7 +34,8 @@ class AuditLog(object):
@classmethod
def get_portfolio_events(cls, portfolio, pagination_opts=None):
return AuditEventQuery.get_ws_events(portfolio.id, pagination_opts)
return AuditEventQuery.get_portfolio_events(portfolio.id, pagination_opts)
@classmethod
def get_by_resource(cls, resource_id):

View File

@ -17,11 +17,17 @@ class AuditEvent(Base, TimestampsMixin):
portfolio_id = Column(UUID(as_uuid=True), ForeignKey("portfolios.id"), index=True)
portfolio = relationship("Portfolio", backref="audit_events")
application_id = Column(
UUID(as_uuid=True), ForeignKey("applications.id"), index=True
)
application = relationship("Application", backref="audit_events")
changed_state = Column(JSONB())
event_details = Column(JSONB())
resource_type = Column(String(), nullable=False)
resource_id = Column(UUID(as_uuid=True), index=True, nullable=False)
display_name = Column(String())
action = Column(String(), nullable=False)
@ -29,6 +35,7 @@ class AuditEvent(Base, TimestampsMixin):
def log(self):
return {
"portfolio_id": str(self.portfolio_id),
"application_id": str(self.application_id),
"changed_state": self.changed_state,
"event_details": self.event_details,
"resource_type": self.resource_type,

View File

@ -13,17 +13,27 @@ class AuditableMixin(object):
@staticmethod
def create_audit_event(connection, resource, action, changed_state=None):
user_id = getattr_path(g, "current_user.id")
portfolio_id = resource.portfolio_id
resource_type = resource.resource_type
display_name = resource.displayname
event_details = resource.event_details
if resource_type == "portfolio":
portfolio_id = resource.id
else:
portfolio_id = resource.portfolio_id
if resource_type == "application":
application_id = resource.id
else:
application_id = resource.application_id
if changed_state is None:
changed_state = resource.history if action == ACTION_UPDATE else None
audit_event = AuditEvent(
user_id=user_id,
portfolio_id=portfolio_id,
application_id=application_id,
resource_type=resource_type,
resource_id=resource.id,
display_name=display_name,
@ -95,6 +105,10 @@ class AuditableMixin(object):
def portfolio_id(self):
return None
@property
def application_id(self):
return None
@property
def displayname(self):
return None

View File

@ -1,5 +1,6 @@
import pytest
from atst.domain.applications import Applications
from atst.domain.audit_log import AuditLog
from atst.domain.exceptions import UnauthorizedError
from atst.domain.permission_sets import PermissionSets
@ -45,7 +46,7 @@ def test_paginate_ws_audit_log():
assert len(events) == 25
def test_ws_audit_log_only_includes_current_ws_events():
def test_portfolio_audit_log_only_includes_current_portfolio_events():
owner = UserFactory.create()
portfolio = PortfolioFactory.create(owner=owner)
other_portfolio = PortfolioFactory.create(owner=owner)
@ -55,8 +56,30 @@ def test_ws_audit_log_only_includes_current_ws_events():
events = AuditLog.get_portfolio_events(portfolio)
for event in events:
assert event.portfolio_id == portfolio.id or event.resource_id == portfolio.id
assert event.portfolio_id == portfolio.id
assert (
not event.portfolio_id == other_portfolio.id
or event.resource_id == other_portfolio.id
)
def test_portfolio_audit_log_includes_app_and_env_events():
# TODO: add in events for
# creating/updating/deleting env_role and app_role
# creating an app_invitation
owner = UserFactory.create()
portfolio = PortfolioFactory.create(owner=owner)
application = ApplicationFactory.create(portfolio=portfolio)
Applications.update(application, {"name": "Star Cruiser"})
events = AuditLog.get_portfolio_events(portfolio)
for event in events:
assert event.portfolio_id == portfolio.id
if event.resource_type == 'application':
assert event.application_id == application.id
pass
def test_application_audit_log_does_not_include_portfolio_events():
pass