From 887c134b2535ba786ce50a9db0099b95121d1f11 Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 13 Nov 2018 16:01:18 -0500 Subject: [PATCH 01/17] Add tests --- atst/models/workspace_role.py | 1 + 1 file changed, 1 insertion(+) diff --git a/atst/models/workspace_role.py b/atst/models/workspace_role.py index 21d17a90..0f496503 100644 --- a/atst/models/workspace_role.py +++ b/atst/models/workspace_role.py @@ -57,6 +57,7 @@ class WorkspaceRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): change_set["status"] = [from_status, to_status] return change_set + @property def event_details(self): return { From f21608ef5ec00f8437ac320476f5b2b140afce0f Mon Sep 17 00:00:00 2001 From: Montana Date: Wed, 14 Nov 2018 15:27:29 -0500 Subject: [PATCH 02/17] Listen for changes to environment roles and only log update events when something has changed --- atst/models/environment_role.py | 2 +- atst/models/mixins/auditable.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/atst/models/environment_role.py b/atst/models/environment_role.py index b236617d..6239bb66 100644 --- a/atst/models/environment_role.py +++ b/atst/models/environment_role.py @@ -10,7 +10,7 @@ class CSPRole(Enum): NONSENSE_ROLE = "nonsense_role" -class EnvironmentRole(Base, mixins.TimestampsMixin): +class EnvironmentRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): __tablename__ = "environment_roles" id = types.Id() diff --git a/atst/models/mixins/auditable.py b/atst/models/mixins/auditable.py index 8aee817a..2c77cc4c 100644 --- a/atst/models/mixins/auditable.py +++ b/atst/models/mixins/auditable.py @@ -52,7 +52,8 @@ class AuditableMixin(object): @staticmethod def audit_update(mapper, connection, target): - target.create_audit_event(connection, target, ACTION_UPDATE) + if AuditableMixin.get_history(target): + target.create_audit_event(connection, target, ACTION_UPDATE) def get_changes(self): """ From d13cf99b32c91c2145d51c2d7fc8e0dd9a2ef4c7 Mon Sep 17 00:00:00 2001 From: Montana Date: Wed, 14 Nov 2018 15:54:20 -0500 Subject: [PATCH 03/17] Add audit log attributes to environment role model --- atst/models/environment_role.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/atst/models/environment_role.py b/atst/models/environment_role.py index 6239bb66..3ae37656 100644 --- a/atst/models/environment_role.py +++ b/atst/models/environment_role.py @@ -29,6 +29,29 @@ class EnvironmentRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): self.role, self.user.full_name, self.environment.name, self.id ) + @property + def history(self): + previous_state = mixins.AuditableMixin.get_history(self) + auditable_previous_state = {} + if "role" in previous_state: + from_role = previous_state["role"] + to_role = self.role + auditable_previous_state["role"] = [from_role, to_role] + return auditable_previous_state + + @property + def event_details(self): + return { + "updated_user": self.user.displayname, + "updated_user_id": str(self.user_id), + "env": self.environment.displayname, + "env_id": str(self.environment_id), + "project": self.environment.project.name, + "project_id": str(self.environment.project_id), + "workspace": self.environment.project.workspace.name, + "workspace_id": str(self.environment.project.workspace.id), + } + Index( "environments_role_user_environment", From e5f183218cb3dd3d2c3fef1440fd478520a9454d Mon Sep 17 00:00:00 2001 From: Montana Date: Wed, 14 Nov 2018 16:09:08 -0500 Subject: [PATCH 04/17] Display audit log event with all information --- atst/models/environment_role.py | 4 ++-- templates/audit_log.html | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/atst/models/environment_role.py b/atst/models/environment_role.py index 3ae37656..bbbcde5f 100644 --- a/atst/models/environment_role.py +++ b/atst/models/environment_role.py @@ -44,8 +44,8 @@ class EnvironmentRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): return { "updated_user": self.user.displayname, "updated_user_id": str(self.user_id), - "env": self.environment.displayname, - "env_id": str(self.environment_id), + "environment": self.environment.displayname, + "environment_id": str(self.environment_id), "project": self.environment.project.name, "project_id": str(self.environment.project_id), "workspace": self.environment.project.workspace.name, diff --git a/templates/audit_log.html b/templates/audit_log.html index 5e679dfe..ba3eae10 100644 --- a/templates/audit_log.html +++ b/templates/audit_log.html @@ -33,9 +33,15 @@
{% endif %} - {% if event.changed_state %} - from {{ event.changed_state.role[0] }} to {{ event.changed_state.role[1] }} -
+ {% if event.event_details["environment"] %} +
+ in Environment {{ event.event_details["environment_id"] }} ({{ event.event_details["environment"] }}) +
+ in Project {{ event.event_details["project_id"] }} ({{ event.event_details["project"] }}) +
+ in Workspace {{ event.event_details["workspace_id"] }} ({{ event.event_details["workspace"] }}) + {% endif %} +
{% endif %} {% if event.workspace %} @@ -43,6 +49,11 @@ {% elif event.request %} on Request {{ event.request_id }} ({{ event.request.displayname }}) {% endif %} + + {% if event.changed_state %} + from {{ event.changed_state.role[0] }} to {{ event.changed_state.role[1] }} +
+ {% endif %} From e984d10ac26e361113473514d2ba43b01ae07b61 Mon Sep 17 00:00:00 2001 From: Montana Date: Mon, 26 Nov 2018 11:46:52 -0500 Subject: [PATCH 05/17] Fixup after rebase --- atst/models/environment_role.py | 12 ++++++------ atst/models/mixins/auditable.py | 7 ++----- templates/audit_log.html | 4 +--- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/atst/models/environment_role.py b/atst/models/environment_role.py index bbbcde5f..da038324 100644 --- a/atst/models/environment_role.py +++ b/atst/models/environment_role.py @@ -31,18 +31,18 @@ class EnvironmentRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): @property def history(self): - previous_state = mixins.AuditableMixin.get_history(self) - auditable_previous_state = {} + previous_state = self.get_changes() + change_set = {} if "role" in previous_state: - from_role = previous_state["role"] + from_role = previous_state["role"][0] to_role = self.role - auditable_previous_state["role"] = [from_role, to_role] - return auditable_previous_state + change_set["role"] = [from_role, to_role] + return change_set @property def event_details(self): return { - "updated_user": self.user.displayname, + "updated_user_name": self.user.displayname, "updated_user_id": str(self.user_id), "environment": self.environment.displayname, "environment_id": str(self.environment_id), diff --git a/atst/models/mixins/auditable.py b/atst/models/mixins/auditable.py index 2c77cc4c..9d34dece 100644 --- a/atst/models/mixins/auditable.py +++ b/atst/models/mixins/auditable.py @@ -52,15 +52,12 @@ class AuditableMixin(object): @staticmethod def audit_update(mapper, connection, target): - if AuditableMixin.get_history(target): + if AuditableMixin.get_changes(target): target.create_audit_event(connection, target, ACTION_UPDATE) def get_changes(self): """ - This function borrows largely from a gist: - https://gist.github.com/ngse/c20058116b8044c65d3fbceda3fdf423#file-audit_mixin-py-L106-L120 - - It returns a dictionary of the form {item: [from_value, to_value]}, + This function returns a dictionary of the form {item: [from_value, to_value]}, where 'item' is the attribute on the target that has been updated, 'from_value' is the value of the attribute before it was updated, and 'to_value' is the current value of the attribute. diff --git a/templates/audit_log.html b/templates/audit_log.html index ba3eae10..b4b41daa 100644 --- a/templates/audit_log.html +++ b/templates/audit_log.html @@ -30,8 +30,6 @@ {% if event.event_details %} for User {{ event.event_details.updated_user_id }} ({{ event.event_details.updated_user_name }}) -
- {% endif %} {% if event.event_details["environment"] %}
@@ -50,7 +48,7 @@ on Request {{ event.request_id }} ({{ event.request.displayname }}) {% endif %} - {% if event.changed_state %} + {% if event.changed_state.role %} from {{ event.changed_state.role[0] }} to {{ event.changed_state.role[1] }}
{% endif %} From ce669fd08660d4459f990ffabd1f0444ca26a40e Mon Sep 17 00:00:00 2001 From: Montana Date: Mon, 26 Nov 2018 15:28:19 -0500 Subject: [PATCH 06/17] Add tests --- atst/models/workspace_role.py | 1 - tests/models/test_workspace_role.py | 52 +++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/atst/models/workspace_role.py b/atst/models/workspace_role.py index 0f496503..21d17a90 100644 --- a/atst/models/workspace_role.py +++ b/atst/models/workspace_role.py @@ -57,7 +57,6 @@ class WorkspaceRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): change_set["status"] = [from_status, to_status] return change_set - @property def event_details(self): return { diff --git a/tests/models/test_workspace_role.py b/tests/models/test_workspace_role.py index d59b85cc..990c8e0b 100644 --- a/tests/models/test_workspace_role.py +++ b/tests/models/test_workspace_role.py @@ -13,10 +13,13 @@ from tests.factories import ( UserFactory, InvitationFactory, WorkspaceRoleFactory, + EnvironmentFactory, + EnvironmentRoleFactory, + ProjectFactory, ) -def test_has_no_history(session): +def test_has_no_ws_role_history(session): owner = UserFactory.create() user = UserFactory.create() @@ -33,7 +36,7 @@ def test_has_no_history(session): assert not create_event.changed_state -def test_has_role_history(session): +def test_has_ws_role_history(session): owner = UserFactory.create() user = UserFactory.create() @@ -58,7 +61,7 @@ def test_has_role_history(session): assert changed_events[0].changed_state["role"][1] == "admin" -def test_has_status_history(session): +def test_has_ws_status_history(session): owner = UserFactory.create() user = UserFactory.create() @@ -81,6 +84,49 @@ def test_has_status_history(session): assert changed_events[0].changed_state["status"][1] == "active" +def test_has_no_env_role_history(session): + owner = UserFactory.create() + user = UserFactory.create() + workspace = Workspaces.create(RequestFactory.create(creator=owner)) + project = ProjectFactory.create(workspace=workspace) + environment = EnvironmentFactory.create(project=project, name="new environment!") + + env_role = EnvironmentRoleFactory.create( + user=user, environment=environment, role="developer" + ) + create_event = ( + session.query(AuditEvent) + .filter(AuditEvent.resource_id == env_role.id, AuditEvent.action == "create") + .one() + ) + + assert not create_event.changed_state + + +def test_has_env_role_history(session): + owner = UserFactory.create() + user = UserFactory.create() + workspace = Workspaces.create(RequestFactory.create(creator=owner)) + workspace_role = WorkspaceRoleFactory.create(workspace=workspace, user=user) + project = ProjectFactory.create(workspace=workspace) + environment = EnvironmentFactory.create(project=project, name="new environment!") + + env_role = EnvironmentRoleFactory.create( + user=user, environment=environment, role="developer" + ) + Environments.update_environment_roles( + owner, workspace, workspace_role, [{"role": "admin", "id": environment.id}] + ) + changed_events = ( + session.query(AuditEvent) + .filter(AuditEvent.resource_id == env_role.id, AuditEvent.action == "update") + .all() + ) + # changed_state["role"] returns a list [previous role, current role] + assert changed_events[0].changed_state["role"][0] == "developer" + assert changed_events[0].changed_state["role"][1] == "admin" + + def test_event_details(): owner = UserFactory.create() user = UserFactory.create() From 9177d7f3e3bd1d3258971e1f724cc0b83d168f3f Mon Sep 17 00:00:00 2001 From: Montana Date: Mon, 26 Nov 2018 16:30:03 -0500 Subject: [PATCH 07/17] Handle update events when previous state was None --- atst/models/mixins/auditable.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/atst/models/mixins/auditable.py b/atst/models/mixins/auditable.py index 9d34dece..b39c8768 100644 --- a/atst/models/mixins/auditable.py +++ b/atst/models/mixins/auditable.py @@ -17,9 +17,12 @@ class AuditableMixin(object): request_id = resource.auditable_request_id() resource_type = resource.auditable_resource_type() display_name = resource.auditable_displayname() - changed_state = resource.auditable_changed_state() event_details = resource.auditable_event_details() + changed_state = ( + resource.auditable_changed_state() if action == ACTION_UPDATE else None + ) + audit_event = AuditEvent( user_id=user_id, workspace_id=workspace_id, @@ -69,7 +72,9 @@ class AuditableMixin(object): for attr in attrs: history = getattr(inspect(self).attrs, attr.key).history if history.has_changes(): - previous_state[attr.key] = [history.deleted.pop(), history.added.pop()] + deleted = history.deleted.pop() if history.deleted else None + added = history.added.pop() if history.added else None + previous_state[attr.key] = [deleted, added] return previous_state def auditable_changed_state(self): From 0615bcef89606e782f6b55443e06592b61b94810 Mon Sep 17 00:00:00 2001 From: Montana Date: Mon, 26 Nov 2018 16:30:47 -0500 Subject: [PATCH 08/17] Fix a test to work with new history attribute --- tests/domain/test_workspaces.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/domain/test_workspaces.py b/tests/domain/test_workspaces.py index efec7eb3..901089b0 100644 --- a/tests/domain/test_workspaces.py +++ b/tests/domain/test_workspaces.py @@ -134,7 +134,8 @@ def test_update_workspace_role_role(workspace, workspace_owner): "workspace_role": "developer", "dod_id": "1234567890", } - member = Workspaces.create_member(workspace_owner, workspace, user_data) + WorkspaceRoleFactory._meta.sqlalchemy_session_persistence = "flush" + member = WorkspaceRoleFactory.create(workspace=workspace) role_name = "admin" updated_member = Workspaces.update_member( From bcf5d2c1338ae937759fb27dd0e943442c16f1a6 Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 27 Nov 2018 09:27:09 -0500 Subject: [PATCH 09/17] Remove Styleguide --- templates/navigation/global_navigation.html | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/templates/navigation/global_navigation.html b/templates/navigation/global_navigation.html index 245474cb..093b86bd 100644 --- a/templates/navigation/global_navigation.html +++ b/templates/navigation/global_navigation.html @@ -2,17 +2,6 @@