Skip to content

Commit

Permalink
Add an audit view for collaborative analysis workspaces
Browse files Browse the repository at this point in the history
This view shows the results of an access audit for collaborative
analysis workspaces. Add classes to the audit source file that
enable showing this website (e.g., tables, etc.). Add a url and a
template. Note that we haven't figured out how to handle the DCC
writers group yet, so there is a test that fails with a note about
that.
  • Loading branch information
amstilp committed Jan 24, 2024
1 parent 139f61d commit d44a29f
Show file tree
Hide file tree
Showing 7 changed files with 620 additions and 3 deletions.
6 changes: 6 additions & 0 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
path("dbgap/", include("primed.dbgap.urls", namespace="dbgap")),
path("duo/", include("primed.duo.urls", namespace="duo")),
path("cdsa/", include("primed.cdsa.urls", namespace="cdsa")),
path(
"collaborative_analysis/",
include(
"primed.collaborative_analysis.urls", namespace="collaborative_analysis"
),
),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


Expand Down
50 changes: 50 additions & 0 deletions primed/collaborative_analysis/audit.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from dataclasses import dataclass
from typing import Union

import django_tables2 as tables
from anvil_consortium_manager.models import (
Account,
GroupAccountMembership,
GroupGroupMembership,
ManagedGroup,
)
from django.urls import reverse
from django.utils.safestring import mark_safe

from . import models

Expand All @@ -28,6 +30,17 @@ def get_action(self):
"""An indicator of what action needs to be taken."""
return None

def get_table_dictionary(self):
"""Return a dictionary that can be used to populate an instance of `SignedAgreementAccessAuditTable`."""
row = {
"workspace": self.collaborative_analysis_workspace,
"member": self.member,
"note": self.note,
"action": self.get_action(),
"action_url": self.get_action_url(),
}
return row


@dataclass
class VerifiedAccess(AccessAuditResult):
Expand Down Expand Up @@ -91,6 +104,25 @@ def get_action_url(self):
)


class AccessAuditResultsTable(tables.Table):
"""A table to show results from a CollaborativeAnalysisWorkspaceAccessAudit instance."""

workspace = tables.Column(linkify=True)
member = tables.Column(linkify=True)
note = tables.Column()
action = tables.Column()

class Meta:
attrs = {"class": "table align-middle"}

def render_action(self, record, value):
return mark_safe(
"""<a href="{}" class="btn btn-primary btn-sm">{}</a>""".format(
record["action_url"], value
)
)


class CollaborativeAnalysisWorkspaceAccessAudit:
"""Class to audit access to a CollaborativeAnalysisWorkspace."""

Expand All @@ -106,6 +138,8 @@ class CollaborativeAnalysisWorkspaceAccessAudit:
# Errors.
UNEXPECTED_GROUP_ACCESS = "Unexpected group added to the auth domain."

results_table_class = AccessAuditResultsTable

def __init__(self, queryset=None):
"""Initialize the audit.
Expand Down Expand Up @@ -240,3 +274,19 @@ def run_audit(self):
for workspace in self.queryset:
self._audit_workspace(workspace)
self.completed = True

def get_verified_table(self):
"""Return a table of verified results."""
return self.results_table_class(
[x.get_table_dictionary() for x in self.verified]
)

def get_needs_action_table(self):
"""Return a table of results where action is needed."""
return self.results_table_class(
[x.get_table_dictionary() for x in self.needs_action]
)

def get_errors_table(self):
"""Return a table of audit errors."""
return self.results_table_class([x.get_table_dictionary() for x in self.errors])
89 changes: 89 additions & 0 deletions primed/collaborative_analysis/tests/test_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,3 +968,92 @@ def test_queryset(self):
self.assertEqual(record.collaborative_analysis_workspace, workspace_2)
self.assertEqual(record.member, analyst_2)
self.assertEqual(record.note, collab_audit_2.IN_SOURCE_AUTH_DOMAINS)


class AccessAuditResultsTableTest(TestCase):
"""Tests for the `AccessAuditResultsTable` table."""

def test_no_rows(self):
"""Table works with no rows."""
table = audit.AccessAuditResultsTable([])
self.assertIsInstance(table, audit.AccessAuditResultsTable)
self.assertEqual(len(table.rows), 0)

def test_one_row_account(self):
"""Table works with one row with an account member."""
workspace = factories.CollaborativeAnalysisWorkspaceFactory.create()
member_account = AccountFactory.create()
data = [
{
"workspace": workspace,
"member": member_account,
"note": "a note",
"action": "",
"action_url": "",
}
]
table = audit.AccessAuditResultsTable(data)
self.assertIsInstance(table, audit.AccessAuditResultsTable)
self.assertEqual(len(table.rows), 1)

def test_one_row_group(self):
"""Table works with one row with a group member."""
workspace = factories.CollaborativeAnalysisWorkspaceFactory.create()
member_group = ManagedGroupFactory.create()
data = [
{
"workspace": workspace,
"member": member_group,
"note": "a note",
"action": "",
"action_url": "",
}
]
table = audit.AccessAuditResultsTable(data)
self.assertIsInstance(table, audit.AccessAuditResultsTable)
self.assertEqual(len(table.rows), 1)

def test_two_rows(self):
"""Table works with two rows."""
workspace = factories.CollaborativeAnalysisWorkspaceFactory.create()
member_account = AccountFactory.create()
member_group = ManagedGroupFactory.create()
data = [
{
"workspace": workspace,
"member": member_account,
"note": "a note",
"action": "",
"action_url": "",
},
{
"workspace": workspace,
"member": member_group,
"note": "a note",
"action": "",
"action_url": "",
},
]
table = audit.AccessAuditResultsTable(data)
self.assertIsInstance(table, audit.AccessAuditResultsTable)
self.assertEqual(len(table.rows), 2)

def test_render_action(self):
"""Render action works as expected for grant access types."""
workspace = factories.CollaborativeAnalysisWorkspaceFactory.create()
member_group = ManagedGroupFactory.create()
data = [
{
"workspace": workspace,
"member": member_group,
"note": "a note",
"action": "Grant",
"action_url": "foo",
}
]

table = audit.AccessAuditResultsTable(data)
self.assertIsInstance(table, audit.AccessAuditResultsTable)
self.assertEqual(len(table.rows), 1)
self.assertIn("foo", table.rows[0].get_cell("action"))
self.assertIn("Grant", table.rows[0].get_cell("action"))
Loading

0 comments on commit d44a29f

Please sign in to comment.