diff --git a/alembic/versions/c5deba1826be_add_appliction_id_to_auditevent.py b/alembic/versions/c5deba1826be_add_appliction_id_to_auditevent.py new file mode 100644 index 00000000..0dcd9f08 --- /dev/null +++ b/alembic/versions/c5deba1826be_add_appliction_id_to_auditevent.py @@ -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 ### diff --git a/atst/domain/audit_log.py b/atst/domain/audit_log.py index 8fa6e517..cb7354db 100644 --- a/atst/domain/audit_log.py +++ b/atst/domain/audit_log.py @@ -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): diff --git a/atst/models/audit_event.py b/atst/models/audit_event.py index 2d8d4ea1..90c69ee1 100644 --- a/atst/models/audit_event.py +++ b/atst/models/audit_event.py @@ -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, diff --git a/atst/models/mixins/auditable.py b/atst/models/mixins/auditable.py index 091dc411..70c462a2 100644 --- a/atst/models/mixins/auditable.py +++ b/atst/models/mixins/auditable.py @@ -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 diff --git a/tests/domain/test_audit_log.py b/tests/domain/test_audit_log.py index af4f76e0..687b2395 100644 --- a/tests/domain/test_audit_log.py +++ b/tests/domain/test_audit_log.py @@ -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