From 21041b6e06b5c0d57b315ffb7a337cce66e6117c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:24:24 +0000 Subject: [PATCH 001/125] Bump jsonapi-requests from 0.7.0 to 0.8.0 Bumps [jsonapi-requests](https://github.com/socialwifi/jsonapi-requests) from 0.7.0 to 0.8.0. - [Changelog](https://github.com/socialwifi/jsonapi-requests/blob/master/CHANGELOG.md) - [Commits](https://github.com/socialwifi/jsonapi-requests/compare/0.7.0...0.8.0) --- updated-dependencies: - dependency-name: jsonapi-requests dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 1639725f..ed01a7a2 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -94,7 +94,7 @@ google-auth==2.11.0 # via django-anvil-consortium-manager idna==3.7 # via requests -jsonapi-requests==0.7.0 +jsonapi-requests==0.8.0 # via -r requirements/requirements.in mysqlclient==2.2.4 # via -r requirements/requirements.in From 1f087f97512a0e21c017fb6f03169e97f289992d Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 14 Aug 2024 15:45:54 -0700 Subject: [PATCH 002/125] Move audit base code into its own directory We'll be adding other audits, so it's clearer to have them in separate source files in an audit-specific directory. --- gregor_django/gregor_anvil/{audit.py => audit/base.py} | 0 gregor_django/users/audit.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename gregor_django/gregor_anvil/{audit.py => audit/base.py} (100%) diff --git a/gregor_django/gregor_anvil/audit.py b/gregor_django/gregor_anvil/audit/base.py similarity index 100% rename from gregor_django/gregor_anvil/audit.py rename to gregor_django/gregor_anvil/audit/base.py diff --git a/gregor_django/users/audit.py b/gregor_django/users/audit.py index 35fd5668..96b5d7f2 100644 --- a/gregor_django/users/audit.py +++ b/gregor_django/users/audit.py @@ -14,7 +14,7 @@ from requests_oauthlib import OAuth2, OAuth2Session from gregor_django.drupal_oauth_provider.provider import CustomProvider -from gregor_django.gregor_anvil.audit import GREGORAudit, GREGORAuditResult +from gregor_django.gregor_anvil.audit.base import GREGORAudit, GREGORAuditResult from gregor_django.gregor_anvil.models import PartnerGroup, ResearchCenter logger = logging.getLogger(__name__) From 92de740c3f6c47a99af4fc1d3b2931b85c957ebd Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 14 Aug 2024 16:00:55 -0700 Subject: [PATCH 003/125] Rename GREGoR audit classes to have a small o --- gregor_django/gregor_anvil/audit/base.py | 18 +++++++++--------- gregor_django/users/audit.py | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/base.py b/gregor_django/gregor_anvil/audit/base.py index d8387e01..3602a6ef 100644 --- a/gregor_django/gregor_anvil/audit/base.py +++ b/gregor_django/gregor_anvil/audit/base.py @@ -1,17 +1,17 @@ from abc import ABC, abstractmethod, abstractproperty -class GREGORAuditResult(ABC): +class GREGoRAuditResult(ABC): """Abstract base class to hold an audit result for a single check. Subclasses of this class are typically also dataclasses. They can define any number of fields that track information about an audit and its result. The companion RPIMEDAudit class `verified`, `needs_action`, and `errors` attributes should store lists of - GREGORAuditResult instances. + GREGoRAuditResult instances. Typical usage: @dataclass - class MyAuditResult(GREGORAuditResult): + class MyAuditResult(GREGoRAuditResult): some_value: str @@ -27,7 +27,7 @@ def get_table_dictionary(self): ... # pragma: no cover -class GREGORAudit(ABC): +class GREGoRAudit(ABC): """Abstract base class for GREGOR audit classes. This class is intended to be subclassed in order to store all results for a GREGOR audit. @@ -38,14 +38,14 @@ class GREGORAudit(ABC): attributes. Attributes: - verified: A list of GREGORAuditResult subclasses instances that have been verified. - needs_action: A list of GREGORAuditResult subclasses instances that some sort of need action. - errors: A list of GREGORAuditResult subclasses instances where an error has been detected. + verified: A list of GREGoRAuditResult subclasses instances that have been verified. + needs_action: A list of GREGoRAuditResult subclasses instances that some sort of need action. + errors: A list of GREGoRAuditResult subclasses instances where an error has been detected. completed: A boolean indicator of whether the audit has been run. """ # TODO: Add add_verified_result, add_needs_action_result, add_error_result methods. They should - # verify that the result is an instance of GREGORAuditResult (subclass). + # verify that the result is an instance of GREGoRAuditResult (subclass). @abstractproperty def results_table_class(self): @@ -65,7 +65,7 @@ def _run_audit(self): This method should typically loop over a set of instances or checks, and store the results in the `verified`, `needs_action`, and `errors` attributes. The results should - be instances of GREGORAuditResult subclasses. This method should not be called directly. + be instances of GREGoRAuditResult subclasses. This method should not be called directly. When deciding which list to store a result in, consider the following: - verified: The result is as expected and no action is needed. diff --git a/gregor_django/users/audit.py b/gregor_django/users/audit.py index 96b5d7f2..34b4d2d3 100644 --- a/gregor_django/users/audit.py +++ b/gregor_django/users/audit.py @@ -14,7 +14,7 @@ from requests_oauthlib import OAuth2, OAuth2Session from gregor_django.drupal_oauth_provider.provider import CustomProvider -from gregor_django.gregor_anvil.audit.base import GREGORAudit, GREGORAuditResult +from gregor_django.gregor_anvil.audit.base import GREGoRAudit, GREGoRAuditResult from gregor_django.gregor_anvil.models import PartnerGroup, ResearchCenter logger = logging.getLogger(__name__) @@ -43,7 +43,7 @@ class Meta: @dataclass -class UserAuditResult(GREGORAuditResult): +class UserAuditResult(GREGoRAuditResult): local_user: SocialAccount = None anvil_account: Account = None remote_user_data: jsonapi_requests.JsonApiObject = None @@ -118,7 +118,7 @@ class OverDeactivateThresholdUser(UserAuditResult): pass -class UserAudit(GREGORAudit): +class UserAudit(GREGoRAudit): ISSUE_TYPE_USER_INACTIVE = "User is inactive in drupal" ISSUE_TYPE_USER_REMOVED_FROM_SITE = "User removed from site" USER_DEACTIVATE_THRESHOLD = 3 @@ -329,7 +329,7 @@ class Meta: @dataclass -class SiteAuditResult(GREGORAuditResult): +class SiteAuditResult(GREGoRAuditResult): local_site: ResearchCenter remote_site_data: jsonapi_requests.JsonApiObject = None changes: dict = None @@ -377,7 +377,7 @@ class UpdateSite(SiteAuditResult): changes: dict -class SiteAudit(GREGORAudit): +class SiteAudit(GREGoRAudit): ISSUE_TYPE_LOCAL_SITE_INVALID = "Local site is invalid" results_table_class = SiteAuditResultsTable @@ -466,7 +466,7 @@ class Meta: @dataclass -class PartnerGroupAuditResult(GREGORAuditResult): +class PartnerGroupAuditResult(GREGoRAuditResult): local_partner_group: ResearchCenter remote_partner_group_data: jsonapi_requests.JsonApiObject = None changes: dict = None @@ -514,7 +514,7 @@ class UpdatePartnerGroup(PartnerGroupAuditResult): changes: dict -class PartnerGroupAudit(GREGORAudit): +class PartnerGroupAudit(GREGoRAudit): ISSUE_TYPE_LOCAL_PARTNER_GROUP_INVALID = "Local PartnerGroup is invalid" results_table_class = PartnerGroupAuditResultsTable From 5acf24b84bc7bed3aea0cf24644bb6e31eeab066 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 14 Aug 2024 16:08:13 -0700 Subject: [PATCH 004/125] Add tests for the base audit classes --- .../gregor_anvil/tests/test_audit.py | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 gregor_django/gregor_anvil/tests/test_audit.py diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py new file mode 100644 index 00000000..881402c7 --- /dev/null +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -0,0 +1,154 @@ +"""Tests for the `py` module.""" + +from dataclasses import dataclass +from unittest import TestCase + +import django_tables2 as tables +from faker import Faker + +from ..audit.base import GREGoRAudit, GREGoRAuditResult + +# from ..audit import upload_workspace_audit + +fake = Faker() + + +@dataclass +class TempAuditResult(GREGoRAuditResult): + value: str + + def get_table_dictionary(self): + return {"value": self.value} + + +class TempResultsTable(tables.Table): + """A dummy class to use as the results_table_class attribute of GREGoR""" + + # Columns. + value = tables.Column() + + +class TempAudit(GREGoRAudit): + """A dummy class to use for testing the GREGoRAudit class.""" + + # Required abstract properties. + results_table_class = TempResultsTable + + def _run_audit(self): + # For this test, do nothing. + pass + + +class GREGoRAuditResultTest(TestCase): + """Tests for the `GREGoRAuditResult` class.""" + + def test_abstract_base_class(self): + """The abstract base class cannot be instantiated.""" + with self.assertRaises(TypeError): + GREGoRAuditResult() + + def test_instantiation(self): + """Subclass of abstract base class can be instantiated.""" + TempAuditResult(value="foo") + + def test_get_table_dictionary(self): + audit_result = TempAuditResult(value="foo") + self.assertEqual(audit_result.get_table_dictionary(), {"value": "foo"}) + + +class GREGoRAuditTest(TestCase): + """Tests for the `GREGoRAudit` class.""" + + def test_abstract_base_class(self): + """The abstract base class cannot be instantiated.""" + with self.assertRaises(TypeError): + GREGoRAudit() + + def test_instantiation(self): + """Subclass of abstract base class can be instantiated.""" + TempAudit() + + def test_results_lists(self): + """The completed attribute is set appropriately.""" + # Instantiate the class. + audit_results = TempAudit() + self.assertEqual(audit_results.verified, []) + self.assertEqual(audit_results.needs_action, []) + self.assertEqual(audit_results.errors, []) + + def test_completed(self): + """The completed attribute is set appropriately.""" + # Instantiate the class. + audit_results = TempAudit() + self.assertFalse(audit_results.completed) + audit_results.run_audit() + self.assertTrue(audit_results.completed) + + def test_get_all_results(self): + audit_results = TempAudit() + audit_results.run_audit() + # Manually set some audit results to get the output we want. + audit_results.verified = ["a"] + audit_results.needs_action = ["b"] + audit_results.errors = ["c"] + self.assertEqual(audit_results.get_all_results(), ["a", "b", "c"]) + + def test_get_all_results_incomplete(self): + audit_results = TempAudit() + with self.assertRaises(ValueError) as e: + audit_results.get_all_results() + self.assertEqual( + str(e.exception), + "Audit has not been completed. Use run_audit() to run the audit.", + ) + + def test_get_verified_table(self): + audit_results = TempAudit() + audit_results.run_audit() + audit_results.verified = [ + TempAuditResult(value="a"), + ] + audit_results.needs_action = [ + TempAuditResult(value="b"), + ] + audit_results.errors = [ + TempAuditResult(value="c"), + ] + table = audit_results.get_verified_table() + self.assertIsInstance(table, TempResultsTable) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell("value"), "a") + + def test_get_needs_action_table(self): + audit_results = TempAudit() + audit_results.run_audit() + audit_results.verified = [ + TempAuditResult(value="a"), + ] + audit_results.needs_action = [ + TempAuditResult(value="b"), + ] + audit_results.errors = [ + TempAuditResult(value="c"), + ] + table = audit_results.get_needs_action_table() + self.assertIsInstance(table, TempResultsTable) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell("value"), "b") + + def test_get_errors_table(self): + audit_results = TempAudit() + audit_results.run_audit() + audit_results.verified = [ + TempAuditResult(value="a"), + ] + audit_results.needs_action = [ + TempAuditResult(value="b"), + ] + audit_results.errors = [ + TempAuditResult(value="c"), + ] + table = audit_results.get_errors_table() + self.assertIsInstance(table, TempResultsTable) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell("value"), "c") From bf9ddfb44e78a4e451e9ab6c9c0a40cf7aeff979 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 11:59:36 -0700 Subject: [PATCH 005/125] Suppress DEBUG logging in tests The logging level for "root" is DEBUG in base.py, which makes pytest tests run with -s print out lots of unhelpful debug messages. Set the logging level in test.py to be INFO instead of DEBUG. --- config/settings/test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/settings/test.py b/config/settings/test.py index a4b2a4dc..b356314c 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -36,3 +36,6 @@ # get the last templates entry and set debug option TEMPLATES[-1]["OPTIONS"]["debug"] = True # noqa ANVIL_DCC_ADMINS_GROUP_NAME = "TEST_GREGOR_DCC_ADMINS" + +# Suppress DEBUG logging in tests without changing base.py file. +LOGGING["root"]["level"] = "INFO" # noqa: F405 From ea2bda3b74c36d6c1e17018e04e03427060356ec Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 12:07:47 -0700 Subject: [PATCH 006/125] Start adding audits for UploadWorkspaces They are far from complete. --- .../audit/upload_workspace_audit.py | 196 ++++++++++++++++++ .../gregor_anvil/tests/test_audit.py | 49 ++++- 2 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 gregor_django/gregor_anvil/audit/upload_workspace_audit.py diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py new file mode 100644 index 00000000..3c07d228 --- /dev/null +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -0,0 +1,196 @@ +from dataclasses import dataclass + +import django_tables2 as tables +from anvil_consortium_manager.models import ManagedGroup, WorkspaceGroupSharing +from django.conf import settings +from django.db.models import Q, QuerySet +from django.utils import timezone + +from ..models import UploadWorkspace + +# from primed.primed_anvil.tables import BooleanIconColumn +from .base import GREGoRAudit, GREGoRAuditResult + + +@dataclass +class UploadWorkspaceAuditResult(GREGoRAuditResult): + """Base class to hold results for auditing upload workspace sharing.""" + + workspace: UploadWorkspace + note: str + managed_group: ManagedGroup + is_shared: bool + action: str = None + + def get_action_url(self): + """The URL that handles the action needed.""" + # return reverse( + # "gregor_anvil:audit:upload_workspaces:resolve", + # args=[ + # self.dbgap_application.dbgap_project_id, + # self.workspace.workspace.billing_project.name, + # self.workspace.workspace.name, + # ], + # ) + return "" + + def get_table_dictionary(self): + """Return a dictionary that can be used to populate an instance of `dbGaPDataSharingSnapshotAuditTable`.""" + row = { + "workspace": self.workspace, + "managed_group": self.managed_group, + "is_shared": self.is_shared, + "note": self.note, + "action": self.action, + "action_url": self.get_action_url(), + } + return row + + +@dataclass +class VerifiedShared(UploadWorkspaceAuditResult): + """Audit results class for when Sharing has been verified.""" + + has_Sharing: bool = True + + def __str__(self): + return f"Verified sharing: {self.note}" + + +@dataclass +class VerifiedNotShared(UploadWorkspaceAuditResult): + """Audit results class for when no Sharing has been verified.""" + + has_Sharing: bool = False + + def __str__(self): + return f"Verified not shared: {self.note}" + + +@dataclass +class Share(UploadWorkspaceAuditResult): + """Audit results class for when Sharing should be granted.""" + + has_Sharing: bool = False + action: str = "Share" + + def __str__(self): + return f"Share: {self.note}" + + +@dataclass +class StopSharing(UploadWorkspaceAuditResult): + """Audit results class for when Sharing should be removed for a known reason.""" + + has_Sharing: bool = True + action: str = "Stop sharing" + + def __str__(self): + return f"Stop sharing: {self.note}" + + +@dataclass +class Error(UploadWorkspaceAuditResult): + """Audit results class for when an error has been detected (e.g., shared and never should have been).""" + + pass + + +class UploadWorkspaceAuditTable(tables.Table): + """A table to show results from a UploadWorkspaceAudit subclass.""" + + workspace = tables.Column(linkify=True) + managed_group = tables.Column(linkify=True) + is_shared = tables.Column() + note = tables.Column() + action = tables.Column() + # action = tables.TemplateColumn(template_name="gregor_anvil/snippets/upload_workspace_audit_action_button.html") + + class Meta: + attrs = {"class": "table align-middle"} + + +class UploadWorkspaceAudit(GREGoRAudit): + """A class to hold audit results for the GREGoR UploadWorkspace audit.""" + + CURRENT_CYCLE_RC_MEMBER_GROUP = "RC member group has access during current upload cycle." + + results_table_class = UploadWorkspaceAuditTable + + def __init__(self, queryset=None): + super().__init__() + if queryset is None: + queryset = UploadWorkspace.objects.all() + if not (isinstance(queryset, QuerySet) and queryset.model is UploadWorkspace): + raise ValueError("queryset must be a queryset of UploadWorkspace objects.") + self.queryset = queryset + + def _run_audit(self): + for workspace in self.queryset: + self.audit_upload_workspace(workspace) + + def audit_upload_workspace(self, upload_workspace): + """Audit access for a specific UploadWorkspace.""" + # Get a list of managed groups that should be included in this audit. + # This includes the members group for the RC, the DCC groups, GREGOR_ALL, and the auth domain. + research_center = upload_workspace.research_center + groups_to_audit = ManagedGroup.objects.filter( + # RC member or uploader groups. + Q(research_center_of_members=research_center) + | Q(research_center_of_uploaders=research_center) + | + # Specific groups. + Q(name__in=["GREGOR_DCC_WRITERS", "GREGOR_ALL", settings.ANVIL_DCC_ADMINS_GROUP_NAME]) + | + # Auth domain. + Q(authorization_domains__workspace=upload_workspace.workspace) + | + # Groups that the workspace is shared with. + Q(workspace_group_sharing__workspace=upload_workspace.workspace) + ) + + for group in groups_to_audit: + self.audit_workspace_and_group(upload_workspace, group) + + def audit_workspace_and_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and ManagedGroup.""" + # Check if the workspace is from a past, current, or future upload cycle. + if upload_workspace.upload_cycle.start_date > timezone.localdate(): + self._audit_workspace_and_group_for_future_upload_cycle(upload_workspace, managed_group) + elif upload_workspace.upload_cycle.end_date >= timezone.localdate(): + self._audit_workspace_and_group_for_current_upload_cycle(upload_workspace, managed_group) + elif upload_workspace.upload_cycle.end_date < timezone.localdate(): + self._audit_workspace_and_group_for_previous_upload_cycle(upload_workspace, managed_group) + else: + raise ValueError("Upload cycle is not in the past, present, or future.") + + def _audit_workspace_and_group_for_current_upload_cycle(self, upload_workspace, managed_group): + """Audit a workspace from the current upload cycle. + + Groups that should have access are: + - GREGOR_DCC_WRITERS: write access + - GREGOR_DCC_ADMINS: owner access + - RC uploader group: write access + - Auth domain: read access + """ + try: + WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=managed_group) + except WorkspaceGroupSharing.DoesNotExist: + pass + else: + self.verified.append( + VerifiedShared( + workspace=upload_workspace, + note=self.CURRENT_CYCLE_RC_MEMBER_GROUP, + managed_group=managed_group, + is_shared=True, + ) + ) + + # Groups that should have access + + def _audit_workspace_and_group_for_future_upload_cycle(self, upload_workspace, managed_group): + pass + + def _audit_workspace_and_group_for_previous_upload_cycle(self, upload_workspace, managed_group): + pass diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 881402c7..7248310d 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -1,14 +1,16 @@ """Tests for the `py` module.""" from dataclasses import dataclass -from unittest import TestCase import django_tables2 as tables +from anvil_consortium_manager.models import WorkspaceGroupSharing +from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceGroupSharingFactory +from django.test import TestCase from faker import Faker +from ..audit import upload_workspace_audit from ..audit.base import GREGoRAudit, GREGoRAuditResult - -# from ..audit import upload_workspace_audit +from ..tests import factories fake = Faker() @@ -152,3 +154,44 @@ def test_get_errors_table(self): self.assertIsInstance(table, TempResultsTable) self.assertEqual(len(table.rows), 1) self.assertEqual(table.rows[0].get_cell("value"), "c") + + +class UploadWorkspaceAuditTest(TestCase): + """Tests for the `UploadWorkspaceAudit` class.""" + + def test_completed(self): + """The completed attribute is set appropriately.""" + # Instantiate the class. + audit = upload_workspace_audit.UploadWorkspaceAudit() + self.assertFalse(audit.completed) + audit.run_audit() + self.assertTrue(audit.completed) + + def test_no_upload_workspaces(self): + """The audit works if there are no UploadWorkspaces.""" + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_audit_workspace_and_group_current_cycle_members_group_shared(self): + """audit method works with current upload cycle and members group.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__end_date=fake.date_between(start_date="-30d", end_date="+30d") + ) + group = ManagedGroupFactory.create(research_center_of_members=upload_workspace.research_center) + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.CURRENT_CYCLE_RC_MEMBER_GROUP) From 40f96de4e23a027cd8e1bcf991291ff268ac51c6 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 13:09:31 -0700 Subject: [PATCH 007/125] Add some additional unit tests to fill in --- .../audit/upload_workspace_audit.py | 3 ++ .../gregor_anvil/tests/test_audit.py | 47 ++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 3c07d228..c93779a8 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -171,7 +171,10 @@ def _audit_workspace_and_group_for_current_upload_cycle(self, upload_workspace, - GREGOR_DCC_WRITERS: write access - GREGOR_DCC_ADMINS: owner access - RC uploader group: write access + - RC member group: read access - Auth domain: read access + + Note that this audit does not check "can compute" access. """ try: WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=managed_group) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 7248310d..3ec9367b 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -183,7 +183,7 @@ def test_audit_workspace_and_group_current_cycle_members_group_shared(self): group = ManagedGroupFactory.create(research_center_of_members=upload_workspace.research_center) # Share the workspace with the group. WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.WRITER + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.READER ) audit = upload_workspace_audit.UploadWorkspaceAudit() audit.audit_workspace_and_group(upload_workspace, group) @@ -195,3 +195,48 @@ def test_audit_workspace_and_group_current_cycle_members_group_shared(self): self.assertEqual(record.workspace, upload_workspace) self.assertEqual(record.managed_group, group) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.CURRENT_CYCLE_RC_MEMBER_GROUP) + + def test_audit_workspace_and_group_current_cycle_members_group_shared_wrong_access(self): + pass + + def test_audit_workspace_and_group_current_cycle_members_group_not_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_upload_group_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_upload_group_shared_wrong_access(self): + pass + + def test_audit_workspace_and_group_current_cycle_upload_group_not_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_dcc_writers_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_dcc_writers_shared_wrong_access(self): + pass + + def test_audit_workspace_and_group_current_cycle_dcc_writers_not_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_dcc_admin_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_dcc_admin_shared_wrong_access(self): + pass + + def test_audit_workspace_and_group_current_cycle_dcc_admin_not_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_auth_domain_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_auth_domain_shared_wrong_access(self): + pass + + def test_audit_workspace_and_group_current_cycle_auth_domain_not_shared(self): + pass + + def test_audit_workspace_and_group_current_cycle_other_group(self): + pass From 313547800136226d8555bc70bf164a30f76efd32 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 13:20:05 -0700 Subject: [PATCH 008/125] Add an indicator of whether an upload cycle is ready for compute This means that the writers group associated with the RC of the workspace should be granted can_compute access. This indicator can be used in the audit. --- .../0027_uploadcycle_is_ready_for_compute.py | 23 +++++++++++++++++++ gregor_django/gregor_anvil/models.py | 4 ++++ .../gregor_anvil/tests/test_models.py | 9 ++++++++ .../gregor_anvil/uploadcycle_detail.html | 8 +++++++ 4 files changed, 44 insertions(+) create mode 100644 gregor_django/gregor_anvil/migrations/0027_uploadcycle_is_ready_for_compute.py diff --git a/gregor_django/gregor_anvil/migrations/0027_uploadcycle_is_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0027_uploadcycle_is_ready_for_compute.py new file mode 100644 index 00000000..9030988c --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0027_uploadcycle_is_ready_for_compute.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2024-08-15 20:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gregor_anvil', '0026_historicalpartnergroup_drupal_node_id_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='historicaluploadcycle', + name='is_ready_for_compute', + field=models.BooleanField(default=False, help_text='Boolean indicator of whether workspace writers should be able to run compute.'), + ), + migrations.AddField( + model_name='uploadcycle', + name='is_ready_for_compute', + field=models.BooleanField(default=False, help_text='Boolean indicator of whether workspace writers should be able to run compute.'), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 752db5ca..ee59039c 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -145,7 +145,11 @@ class UploadCycle(TimeStampedModel, models.Model): ) start_date = models.DateField(help_text="The start date of this upload cycle.") end_date = models.DateField(help_text="The end date of this upload cycle.") + is_ready_for_compute = models.BooleanField( + help_text="Boolean indicator of whether workspace writers should be able to run compute.", default=False + ) note = models.TextField(blank=True, help_text="Additional notes.") + # Django simple history. history = HistoricalRecords() diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index 27f9833f..a123acaf 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -183,6 +183,7 @@ def test_model_saving(self): instance.full_clean() instance.save() self.assertIsInstance(instance, models.UploadCycle) + self.assertEqual(instance.is_ready_for_compute, False) def test_model_saving_with_note(self): """Creation using the model constructor and .save() works.""" @@ -381,6 +382,14 @@ def test_get_partner_upload_workspaces_full_test(self): self.assertIn(workspace_3, included_workspaces) self.assertNotIn(workspace_4, included_workspaces) + def test_is_ready_for_compute(self): + """UploadCycle is ready for compute if all PartnerUploadWorkspaces have date_completed.""" + upload_cycle = factories.UploadCycleFactory.create() + self.assertFalse(upload_cycle.is_ready_for_compute) + upload_cycle.is_ready_for_compute = True + upload_cycle.save() + self.assertEqual(upload_cycle.is_ready_for_compute, True) + class UploadWorkspaceTest(TestCase): """Tests for the UploadWorkspace model.""" diff --git a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html index f48d3a23..a93dea16 100644 --- a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html @@ -16,6 +16,14 @@
  • Cycle: {{ object.cycle }}
  • Start date: {{ object.start_date }}
  • End date: {{ object.end_date }}
  • +
  • + Ready for compute: + {% if object.is_ready_for_compute %} + Yes + {% else %} + No + {% endif %} +
  • {% endblock panel %} From a6c1234ca77719a9c86ffe53bb97052c4850ddfd Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 15:27:55 -0700 Subject: [PATCH 009/125] Add UploadCycle properties for past, current, and future cycles These are determined based on the start and end dates. --- gregor_django/gregor_anvil/models.py | 16 +++++++ .../gregor_anvil/tests/test_models.py | 46 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index ee59039c..979dee7b 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -3,6 +3,7 @@ from django.core.validators import MinValueValidator from django.db import models from django.urls import reverse +from django.utils import timezone from django_extensions.db.models import TimeStampedModel from simple_history.models import HistoricalRecords @@ -187,6 +188,21 @@ def get_partner_upload_workspaces(self): pks_to_keep.append(instance.pk) return qs.filter(pk__in=pks_to_keep) + @property + def is_current(self): + """Return a boolean indicating whether this upload cycle is the current one.""" + return self.start_date <= timezone.localdate() and self.end_date >= timezone.localdate() + + @property + def is_past(self): + """Return a boolean indicating whether this upload cycle is a past cycle.""" + return self.end_date < timezone.localdate() + + @property + def is_future(self): + """Return a boolean indicating whether this upload cycle is a future cycle.""" + return self.start_date > timezone.localdate() + class UploadWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track additional data about an upload workspace.""" diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index a123acaf..bdd3c43e 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -4,10 +4,14 @@ from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from django.db.utils import IntegrityError from django.test import TestCase +from django.utils import timezone +from faker import Faker from .. import models from . import factories +fake = Faker() + class ConsentGroupTest(TestCase): """Tests for the ConsentGroup model.""" @@ -390,6 +394,48 @@ def test_is_ready_for_compute(self): upload_cycle.save() self.assertEqual(upload_cycle.is_ready_for_compute, True) + def test_is_current_is_past_is_future(self): + # Previous cycle. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate() - timedelta(days=40), + end_date=timezone.localdate() - timedelta(days=10), + ) + self.assertTrue(instance.is_past) + self.assertFalse(instance.is_current) + self.assertFalse(instance.is_future) + # Current cycle, end date today. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate(), + ) + self.assertFalse(instance.is_past) + self.assertTrue(instance.is_current) + self.assertFalse(instance.is_future) + # Current cycle. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=10), + ) + self.assertFalse(instance.is_past) + self.assertTrue(instance.is_current) + self.assertFalse(instance.is_future) + # Current cycle, start date today. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate(), + end_date=timezone.localdate() + timedelta(days=10), + ) + self.assertFalse(instance.is_past) + self.assertTrue(instance.is_current) + self.assertFalse(instance.is_future) + # Future cycle. + instance = factories.UploadCycleFactory.create( + start_date=timezone.localdate() + timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=40), + ) + self.assertFalse(instance.is_past) + self.assertFalse(instance.is_current) + self.assertTrue(instance.is_future) + class UploadWorkspaceTest(TestCase): """Tests for the UploadWorkspace model.""" From cd3f8d344c89c56ad67943fdd6b40dc018145bb1 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 16:05:32 -0700 Subject: [PATCH 010/125] Add is_current, is_past, and is_future params to UploadCycleFactory This makes it easier to test the audits - we don't have to figure out the start and end dates for each upload cycle created in the audit tests. We can just use a parameter to set them appropriately. --- gregor_django/gregor_anvil/tests/factories.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 5b77b97b..6d222565 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -1,7 +1,8 @@ from datetime import timedelta from anvil_consortium_manager.tests.factories import WorkspaceFactory -from factory import Faker, LazyAttribute, Sequence, SubFactory +from django.utils import timezone +from factory import Faker, LazyAttribute, Sequence, SubFactory, Trait from factory.django import DjangoModelFactory from .. import models @@ -39,6 +40,18 @@ class UploadCycleFactory(DjangoModelFactory): class Params: duration = 90 + is_past = Trait( + start_date=timezone.localdate() - timedelta(days=100), + end_date=timezone.localdate() - timedelta(days=10), + ) + is_current = Trait( + start_date=timezone.localdate() - timedelta(days=45), + end_date=timezone.localdate() - timedelta(days=45), + ) + is_future = Trait( + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate() - timedelta(days=100), + ) class Meta: model = models.UploadCycle From 60d8b679841c577cce072a6730a6e0cfa2d064c0 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 16:07:24 -0700 Subject: [PATCH 011/125] Start reworking UploadWorkspace audits Instead of running a sub-method based on temporality of the upload cycle, define sub-methods based on the group type. This will make the logic clearer. Add the sub-method for auditing the RC member group. --- .../audit/upload_workspace_audit.py | 128 +++++++---- .../gregor_anvil/tests/test_audit.py | 214 ++++++++++++++---- 2 files changed, 251 insertions(+), 91 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index c93779a8..88ac560d 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -4,7 +4,6 @@ from anvil_consortium_manager.models import ManagedGroup, WorkspaceGroupSharing from django.conf import settings from django.db.models import Q, QuerySet -from django.utils import timezone from ..models import UploadWorkspace @@ -19,8 +18,8 @@ class UploadWorkspaceAuditResult(GREGoRAuditResult): workspace: UploadWorkspace note: str managed_group: ManagedGroup - is_shared: bool action: str = None + current_sharing_instance: WorkspaceGroupSharing = None def get_action_url(self): """The URL that handles the action needed.""" @@ -43,6 +42,7 @@ def get_table_dictionary(self): "note": self.note, "action": self.action, "action_url": self.get_action_url(), + "current_sharing_instance": self.current_sharing_instance, } return row @@ -51,7 +51,7 @@ def get_table_dictionary(self): class VerifiedShared(UploadWorkspaceAuditResult): """Audit results class for when Sharing has been verified.""" - has_Sharing: bool = True + is_shared: bool = True def __str__(self): return f"Verified sharing: {self.note}" @@ -61,28 +61,61 @@ def __str__(self): class VerifiedNotShared(UploadWorkspaceAuditResult): """Audit results class for when no Sharing has been verified.""" - has_Sharing: bool = False + is_shared: bool = False def __str__(self): return f"Verified not shared: {self.note}" @dataclass -class Share(UploadWorkspaceAuditResult): - """Audit results class for when Sharing should be granted.""" +class ShareAsReader(UploadWorkspaceAuditResult): + """Audit results class for when Sharing should be granted as a reader.""" - has_Sharing: bool = False - action: str = "Share" + is_shared: bool = False + action: str = "Share as reader" def __str__(self): - return f"Share: {self.note}" + return f"Share as reader: {self.note}" + + +@dataclass +class ShareAsWriter(UploadWorkspaceAuditResult): + """Audit results class for when Sharing should be granted as a writer.""" + + is_shared: bool = False + action: str = "Share as writer" + + def __str__(self): + return f"Share as writer: {self.note}" + + +@dataclass +class ShareAsOwner(UploadWorkspaceAuditResult): + """Audit results class for when Sharing should be granted as an owner.""" + + is_shared: bool = False + action: str = "Share as owner" + + def __str__(self): + return f"Share as owner: {self.note}" + + +@dataclass +class ShareWithCompute(UploadWorkspaceAuditResult): + """Audit results class for when Sharing should be granted with compute access.""" + + is_shared: bool = False + action: str = "Share with compute" + + def __str__(self): + return f"Share with compute: {self.note}" @dataclass class StopSharing(UploadWorkspaceAuditResult): """Audit results class for when Sharing should be removed for a known reason.""" - has_Sharing: bool = True + is_shared: bool = True action: str = "Stop sharing" def __str__(self): @@ -104,6 +137,7 @@ class UploadWorkspaceAuditTable(tables.Table): is_shared = tables.Column() note = tables.Column() action = tables.Column() + current_sharing_instance = tables.Column(linkify=True) # action = tables.TemplateColumn(template_name="gregor_anvil/snippets/upload_workspace_audit_action_button.html") class Meta: @@ -113,7 +147,13 @@ class Meta: class UploadWorkspaceAudit(GREGoRAudit): """A class to hold audit results for the GREGoR UploadWorkspace audit.""" - CURRENT_CYCLE_RC_MEMBER_GROUP = "RC member group has access during current upload cycle." + # Reasons for access/sharing. + RC_MEMBERS_GROUP_AS_READER = "RC member group should have read access." + RC_UPLOADER_GROUP_AS_READER = "RC upload group should have read access to past cycles." + RC_UPLOADER_GROUP_AS_WRITER = "RC upload group should have write access for current and future cycles." + RC_UPLOADER_GROUP_WITH_COMPUTE = "RC upload group should be able to run compute for the current cycle." + + # Other errors results_table_class = UploadWorkspaceAuditTable @@ -154,46 +194,46 @@ def audit_upload_workspace(self, upload_workspace): def audit_workspace_and_group(self, upload_workspace, managed_group): """Audit access for a specific UploadWorkspace and ManagedGroup.""" - # Check if the workspace is from a past, current, or future upload cycle. - if upload_workspace.upload_cycle.start_date > timezone.localdate(): - self._audit_workspace_and_group_for_future_upload_cycle(upload_workspace, managed_group) - elif upload_workspace.upload_cycle.end_date >= timezone.localdate(): - self._audit_workspace_and_group_for_current_upload_cycle(upload_workspace, managed_group) - elif upload_workspace.upload_cycle.end_date < timezone.localdate(): - self._audit_workspace_and_group_for_previous_upload_cycle(upload_workspace, managed_group) - else: - raise ValueError("Upload cycle is not in the past, present, or future.") - - def _audit_workspace_and_group_for_current_upload_cycle(self, upload_workspace, managed_group): - """Audit a workspace from the current upload cycle. + # Check the group type, and then call the appropriate audit method. + if upload_workspace.research_center.member_group == managed_group: + self._audit_workspace_and_rc_member_group(upload_workspace, managed_group) + if upload_workspace.research_center.uploader_group == managed_group: + self._audit_workspace_and_rc_uploader_group(upload_workspace, managed_group) - Groups that should have access are: - - GREGOR_DCC_WRITERS: write access - - GREGOR_DCC_ADMINS: owner access - - RC uploader group: write access - - RC member group: read access - - Auth domain: read access + def _audit_workspace_and_rc_member_group(self, upload_workspace, managed_group): + """Run an audit of the upload workspace for the RC member group. - Note that this audit does not check "can compute" access. - """ + The RC member group should always have read access.""" try: - WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=managed_group) + sharing_instance = WorkspaceGroupSharing.objects.get( + workspace=upload_workspace.workspace, group=managed_group + ) except WorkspaceGroupSharing.DoesNotExist: - pass - else: + self.needs_action.append( + ShareAsReader( + workspace=upload_workspace, + note=self.RC_MEMBERS_GROUP_AS_READER, + managed_group=managed_group, + current_sharing_instance=None, + ) + ) + return + + if sharing_instance.access == sharing_instance.READER: self.verified.append( VerifiedShared( workspace=upload_workspace, - note=self.CURRENT_CYCLE_RC_MEMBER_GROUP, + note=self.RC_MEMBERS_GROUP_AS_READER, managed_group=managed_group, - is_shared=True, + current_sharing_instance=sharing_instance, + ) + ) + else: + self.needs_action.append( + ShareAsReader( + workspace=upload_workspace, + note=self.RC_MEMBERS_GROUP_AS_READER, + managed_group=managed_group, + current_sharing_instance=sharing_instance, ) ) - - # Groups that should have access - - def _audit_workspace_and_group_for_future_upload_cycle(self, upload_workspace, managed_group): - pass - - def _audit_workspace_and_group_for_previous_upload_cycle(self, upload_workspace, managed_group): - pass diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 3ec9367b..3776061c 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -157,7 +157,7 @@ def test_get_errors_table(self): class UploadWorkspaceAuditTest(TestCase): - """Tests for the `UploadWorkspaceAudit` class.""" + """General tests of the `UploadWorkspaceAudit` class.""" def test_completed(self): """The completed attribute is set appropriately.""" @@ -175,68 +175,188 @@ def test_no_upload_workspaces(self): self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) - def test_audit_workspace_and_group_current_cycle_members_group_shared(self): + +class UploadWorkspaceAuditRCMemberGroupTest(TestCase): + """Specific tests for the `UploadWorkspaceAudit` class for the member group from the UploadWorkspace's RC.""" + + def setUp(self): + super().setUp() + self.managed_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(member_group=self.managed_group) + + def test_audit_workspace_and_group_current_cycle_shared(self): """audit method works with current upload cycle and members group.""" + # Share the workspace with the group. upload_workspace = factories.UploadWorkspaceFactory.create( - upload_cycle__end_date=fake.date_between(start_date="-30d", end_date="+30d") + research_center=self.research_center, upload_cycle__is_current=True ) - group = ManagedGroupFactory.create(research_center_of_members=upload_workspace.research_center) - # Share the workspace with the group. - WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.READER + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.READER ) audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, group) + audit.audit_workspace_and_group(upload_workspace, self.managed_group) self.assertEqual(len(audit.verified), 1) self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) record = audit.verified[0] self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, group) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.CURRENT_CYCLE_RC_MEMBER_GROUP) - - def test_audit_workspace_and_group_current_cycle_members_group_shared_wrong_access(self): - pass - - def test_audit_workspace_and_group_current_cycle_members_group_not_shared(self): - pass - - def test_audit_workspace_and_group_current_cycle_upload_group_shared(self): - pass - - def test_audit_workspace_and_group_current_cycle_upload_group_shared_wrong_access(self): - pass - - def test_audit_workspace_and_group_current_cycle_upload_group_not_shared(self): - pass - - def test_audit_workspace_and_group_current_cycle_dcc_writers_shared(self): - pass - - def test_audit_workspace_and_group_current_cycle_dcc_writers_shared_wrong_access(self): - pass + self.assertEqual(record.managed_group, self.managed_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - def test_audit_workspace_and_group_current_cycle_dcc_writers_not_shared(self): - pass + def test_audit_workspace_and_group_current_cycle_shared_wrong_access(self): + """audit method works with current upload cycle and members group with wrong access.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_current=True + ) + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, self.managed_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, self.managed_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - def test_audit_workspace_and_group_current_cycle_dcc_admin_shared(self): - pass + def test_audit_workspace_and_group_current_cycle_not_shared(self): + """audit method works with current upload cycle and members group.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_current=True + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, self.managed_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, self.managed_group) + self.assertIsNone(record.current_sharing_instance) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - def test_audit_workspace_and_group_current_cycle_dcc_admin_shared_wrong_access(self): - pass + def test_audit_workspace_and_group_past_cycle_shared(self): + """audit method works with current upload cycle and members group.""" + # Share the workspace with the group. + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_past=True + ) + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, self.managed_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, self.managed_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - def test_audit_workspace_and_group_current_cycle_dcc_admin_not_shared(self): - pass + def test_audit_workspace_and_group_past_cycle_shared_wrong_access(self): + """audit method works with current upload cycle and members group with wrong access.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_past=True + ) + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, self.managed_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, self.managed_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - def test_audit_workspace_and_group_current_cycle_auth_domain_shared(self): - pass + def test_audit_workspace_and_group_past_cycle_not_shared(self): + """audit method works with current upload cycle and members group.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_past=True + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, self.managed_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, self.managed_group) + self.assertIsNone(record.current_sharing_instance) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - def test_audit_workspace_and_group_current_cycle_auth_domain_shared_wrong_access(self): - pass + def test_audit_workspace_and_group_future_cycle_shared(self): + """audit method works with current upload cycle and members group.""" + # Share the workspace with the group. + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_future=True + ) + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, self.managed_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, self.managed_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - def test_audit_workspace_and_group_current_cycle_auth_domain_not_shared(self): - pass + def test_audit_workspace_and_group_future_cycle_shared_wrong_access(self): + """audit method works with future upload cycle and members group with wrong access.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_future=True + ) + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, self.managed_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, self.managed_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - def test_audit_workspace_and_group_current_cycle_other_group(self): - pass + def test_audit_workspace_and_group_future_cycle_not_shared(self): + """audit method works with future upload cycle and members group.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_future=True + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(upload_workspace, self.managed_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, self.managed_group) + self.assertIsNone(record.current_sharing_instance) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) From 2cab419b0b8dff6474473ec56c0e2e27ec734736 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 16:27:24 -0700 Subject: [PATCH 012/125] Add field indicating whether QC is complete for an UploadWorkspace The field tracks the date when QC was completed. This will also be used in audits. --- .../0028_uploadworkspace_date_qc_complete.py | 23 +++++++++++++++++++ gregor_django/gregor_anvil/models.py | 7 ++++++ .../gregor_anvil/tests/test_models.py | 1 + .../gregor_anvil/uploadworkspace_detail.html | 8 ++++++- 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 gregor_django/gregor_anvil/migrations/0028_uploadworkspace_date_qc_complete.py diff --git a/gregor_django/gregor_anvil/migrations/0028_uploadworkspace_date_qc_complete.py b/gregor_django/gregor_anvil/migrations/0028_uploadworkspace_date_qc_complete.py new file mode 100644 index 00000000..c9863efd --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0028_uploadworkspace_date_qc_complete.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2024-08-15 23:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gregor_anvil', '0027_uploadcycle_is_ready_for_compute'), + ] + + operations = [ + migrations.AddField( + model_name='historicaluploadworkspace', + name='date_qc_completed', + field=models.DateTimeField(blank=True, default=None, help_text='Date that QC was completed for this workspace. If null, QC is not complete.', null=True), + ), + migrations.AddField( + model_name='uploadworkspace', + name='date_qc_completed', + field=models.DateTimeField(blank=True, default=None, help_text='Date that QC was completed for this workspace. If null, QC is not complete.', null=True), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 979dee7b..02bf7ecb 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -217,6 +217,13 @@ class UploadWorkspace(TimeStampedModel, BaseWorkspaceData): upload_cycle = models.ForeignKey(UploadCycle, on_delete=models.PROTECT) """The UploadCycle associated with this workspace.""" + date_qc_completed = models.DateTimeField( + help_text="Date that QC was completed for this workspace. If null, QC is not complete.", + blank=True, + null=True, + default=None, + ) + class Meta: constraints = [ # Model uniqueness. diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index bdd3c43e..95b79e55 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -454,6 +454,7 @@ def test_model_saving(self): ) instance.save() self.assertIsInstance(instance, models.UploadWorkspace) + self.assertIsNone(instance.date_qc_completed) def test_str_method(self): """The custom __str__ method returns the correct string.""" diff --git a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html index e556e626..5bc071a2 100644 --- a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html @@ -6,7 +6,13 @@
    Research Center
    {{ object.uploadworkspace.research_center }}
    Consent group
    {{ object.uploadworkspace.consent_group }}
    Upload cycle
    {{ object.uploadworkspace.upload_cycle }}
    -
    Note
    {{ object.note }}
    +
    Date QC completed
    + {% if object.date_qc_completed %} + {{ object.date_qc_completed }} + {% else %} + — + {% endif %} +
    {% endblock workspace_data %} From 6f1988e40dd2a7a3ffd4d2e47e60e0ee3c4458c1 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 15 Aug 2024 16:48:17 -0700 Subject: [PATCH 013/125] Start reworking UploadWorkspace audits again Previously I had started with the RC member group, but an upload workspace should never be shared directly with the member group; that will happen via sharing with the auth domain. Also start redoing tests such that the tests are split into different classes by UploadWorkspace stage. This is a little clearer to program and test, because different sharing is expected at different stages. The audits themselves are still set up to run sub-methods by group type, though. --- .../audit/upload_workspace_audit.py | 63 +++--- .../gregor_anvil/tests/test_audit.py | 197 ++++++------------ 2 files changed, 92 insertions(+), 168 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 88ac560d..1bfb160a 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -148,12 +148,9 @@ class UploadWorkspaceAudit(GREGoRAudit): """A class to hold audit results for the GREGoR UploadWorkspace audit.""" # Reasons for access/sharing. - RC_MEMBERS_GROUP_AS_READER = "RC member group should have read access." - RC_UPLOADER_GROUP_AS_READER = "RC upload group should have read access to past cycles." - RC_UPLOADER_GROUP_AS_WRITER = "RC upload group should have write access for current and future cycles." - RC_UPLOADER_GROUP_WITH_COMPUTE = "RC upload group should be able to run compute for the current cycle." - - # Other errors + RC_UPLOADERS_FUTURE_CYCLE = "Uploaders should have write access for future cycles." + RC_UPLOADERS_BEFORE_COMPUTE = "Uploaders should have write access before compute is enabled for this upload cycle." + RC_UPLOADERS_WITH_COMPUTE = "Uploaders should have write access with compute for this upload cycle." results_table_class = UploadWorkspaceAuditTable @@ -175,9 +172,8 @@ def audit_upload_workspace(self, upload_workspace): # This includes the members group for the RC, the DCC groups, GREGOR_ALL, and the auth domain. research_center = upload_workspace.research_center groups_to_audit = ManagedGroup.objects.filter( - # RC member or uploader groups. - Q(research_center_of_members=research_center) - | Q(research_center_of_uploaders=research_center) + # RC uploader group. + Q(research_center_of_uploaders=research_center) | # Specific groups. Q(name__in=["GREGOR_DCC_WRITERS", "GREGOR_ALL", settings.ANVIL_DCC_ADMINS_GROUP_NAME]) @@ -195,45 +191,50 @@ def audit_upload_workspace(self, upload_workspace): def audit_workspace_and_group(self, upload_workspace, managed_group): """Audit access for a specific UploadWorkspace and ManagedGroup.""" # Check the group type, and then call the appropriate audit method. - if upload_workspace.research_center.member_group == managed_group: - self._audit_workspace_and_rc_member_group(upload_workspace, managed_group) if upload_workspace.research_center.uploader_group == managed_group: self._audit_workspace_and_rc_uploader_group(upload_workspace, managed_group) - def _audit_workspace_and_rc_member_group(self, upload_workspace, managed_group): - """Run an audit of the upload workspace for the RC member group. - - The RC member group should always have read access.""" + def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and RC uploader group.""" try: - sharing_instance = WorkspaceGroupSharing.objects.get( + current_sharing = WorkspaceGroupSharing.objects.get( workspace=upload_workspace.workspace, group=managed_group ) except WorkspaceGroupSharing.DoesNotExist: self.needs_action.append( - ShareAsReader( + ShareAsWriter( workspace=upload_workspace, - note=self.RC_MEMBERS_GROUP_AS_READER, managed_group=managed_group, - current_sharing_instance=None, + note=self.RC_UPLOADERS_FUTURE_CYCLE, ) ) return - - if sharing_instance.access == sharing_instance.READER: - self.verified.append( - VerifiedShared( - workspace=upload_workspace, - note=self.RC_MEMBERS_GROUP_AS_READER, - managed_group=managed_group, - current_sharing_instance=sharing_instance, + # Make sure sharing is correct. + if current_sharing.access == WorkspaceGroupSharing.WRITER: + if current_sharing.can_compute: + self.needs_action.append( + ShareAsWriter( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_UPLOADERS_FUTURE_CYCLE, + current_sharing_instance=current_sharing, + ) + ) + else: + self.verified.append( + VerifiedShared( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_UPLOADERS_FUTURE_CYCLE, + current_sharing_instance=current_sharing, + ) ) - ) else: self.needs_action.append( - ShareAsReader( + ShareAsWriter( workspace=upload_workspace, - note=self.RC_MEMBERS_GROUP_AS_READER, managed_group=managed_group, - current_sharing_instance=sharing_instance, + note=self.RC_UPLOADERS_FUTURE_CYCLE, + current_sharing_instance=current_sharing, ) ) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 3776061c..17d7a4f4 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -176,187 +176,110 @@ def test_no_upload_workspaces(self): self.assertEqual(len(audit.errors), 0) -class UploadWorkspaceAuditRCMemberGroupTest(TestCase): - """Specific tests for the `UploadWorkspaceAudit` class for the member group from the UploadWorkspace's RC.""" +class UploadWorkspaceAuditFutureCycleTest(TestCase): + """Tests for the `UploadWorkspaceAudit` class for future cycle UploadWorkspaces.""" def setUp(self): super().setUp() - self.managed_group = ManagedGroupFactory.create() - self.research_center = factories.ResearchCenterFactory.create(member_group=self.managed_group) + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_future=True + ) - def test_audit_workspace_and_group_current_cycle_shared(self): - """audit method works with current upload cycle and members group.""" + def test_uploaders_shared_as_writer_no_compute(self): + """audit method works with future upload cycle and group before compute is enabled.""" # Share the workspace with the group. - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_current=True - ) sharing = WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.READER + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER ) audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 1) self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) record = audit.verified[0] self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE) - def test_audit_workspace_and_group_current_cycle_shared_wrong_access(self): - """audit method works with current upload cycle and members group with wrong access.""" - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_current=True - ) - # Share the workspace with the group. - sharing = WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.WRITER - ) + def test_uploaders_not_shared(self): audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) - self.assertEqual(record.current_sharing_instance, sharing) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) + self.assertIsInstance(record, upload_workspace_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE) - def test_audit_workspace_and_group_current_cycle_not_shared(self): - """audit method works with current upload cycle and members group.""" - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_current=True + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, ) audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) - self.assertIsNone(record.current_sharing_instance) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - - def test_audit_workspace_and_group_past_cycle_shared(self): - """audit method works with current upload cycle and members group.""" - # Share the workspace with the group. - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_past=True - ) - sharing = WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.READER - ) - audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) - self.assertEqual(len(audit.verified), 1) - self.assertEqual(len(audit.needs_action), 0) - self.assertEqual(len(audit.errors), 0) - record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) + self.assertIsInstance(record, upload_workspace_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE) - def test_audit_workspace_and_group_past_cycle_shared_wrong_access(self): - """audit method works with current upload cycle and members group with wrong access.""" - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_past=True - ) - # Share the workspace with the group. + def test_uploaders_shared_as_reader(self): sharing = WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.WRITER + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER ) audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) + self.assertIsInstance(record, upload_workspace_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE) - def test_audit_workspace_and_group_past_cycle_not_shared(self): - """audit method works with current upload cycle and members group.""" - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_past=True - ) - audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) - self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) - self.assertIsNone(record.current_sharing_instance) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - - def test_audit_workspace_and_group_future_cycle_shared(self): - """audit method works with current upload cycle and members group.""" - # Share the workspace with the group. - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_future=True - ) + def test_uploaders_shared_as_owner(self): sharing = WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.READER + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER ) audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) - self.assertEqual(len(audit.verified), 1) - self.assertEqual(len(audit.needs_action), 0) - self.assertEqual(len(audit.errors), 0) - record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) - self.assertEqual(record.current_sharing_instance, sharing) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) - - def test_audit_workspace_and_group_future_cycle_shared_wrong_access(self): - """audit method works with future upload cycle and members group with wrong access.""" - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_future=True - ) - # Share the workspace with the group. - sharing = WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=self.managed_group, access=WorkspaceGroupSharing.WRITER - ) - audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) + self.assertIsInstance(record, upload_workspace_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE) - def test_audit_workspace_and_group_future_cycle_not_shared(self): - """audit method works with future upload cycle and members group.""" - upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_future=True - ) - audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.audit_workspace_and_group(upload_workspace, self.managed_group) - self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) - self.assertEqual(record.workspace, upload_workspace) - self.assertEqual(record.managed_group, self.managed_group) - self.assertIsNone(record.current_sharing_instance) - self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_MEMBERS_GROUP_AS_READER) + def test_dcc_writers(self): + pass + + def test_dcc_admin(self): + pass + + def test_anvil_groups(self): + pass + + def test_gregor_all(self): + pass + + def test_unexpected_group(self): + pass From d2b16909a93513e815baf70613fca5602649cde9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:39:06 +0000 Subject: [PATCH 014/125] Bump ruff from 0.5.7 to 0.6.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.7 to 0.6.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.5.7...0.6.1) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index f996e7ec..16e31a22 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -150,7 +150,7 @@ requests==2.32.3 # -c requirements/requirements.txt # -c requirements/test-requirements.txt # sphinx -ruff==0.5.7 +ruff==0.6.1 # via -r requirements/dev-requirements.in six==1.16.0 # via From 13b9582e26cb26d6f03883cc4319e064e67e25ba Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 19 Aug 2024 14:55:23 -0700 Subject: [PATCH 015/125] Add RC uploader checks for the current upload cycle --- .../audit/upload_workspace_audit.py | 100 ++++++--- gregor_django/gregor_anvil/tests/factories.py | 6 +- .../gregor_anvil/tests/test_audit.py | 212 +++++++++++++++++- 3 files changed, 285 insertions(+), 33 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 1bfb160a..340e55f7 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -149,8 +149,10 @@ class UploadWorkspaceAudit(GREGoRAudit): # Reasons for access/sharing. RC_UPLOADERS_FUTURE_CYCLE = "Uploaders should have write access for future cycles." - RC_UPLOADERS_BEFORE_COMPUTE = "Uploaders should have write access before compute is enabled for this upload cycle." - RC_UPLOADERS_WITH_COMPUTE = "Uploaders should have write access with compute for this upload cycle." + RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE = ( + "Uploaders should have write access before compute is enabled for this upload cycle." + ) + RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE = "Uploaders should have write access with compute for this upload cycle." results_table_class = UploadWorkspaceAuditTable @@ -195,46 +197,86 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_rc_uploader_group(upload_workspace, managed_group) def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group): - """Audit access for a specific UploadWorkspace and RC uploader group.""" + """Audit access for a specific UploadWorkspace and RC uploader group. + + Sharing expectations: + - Write access to future upload cycle. + - Write access before compute is enabled for current upload cycle. + - Write+compute access after compute is enabled for current upload cycle. + - Read access to past upload cycle workspaces before QC is completed. + - No access to past upload cycle workspaces after QC is completed (read access via auth domain). + """ + upload_cycle = upload_workspace.upload_cycle try: current_sharing = WorkspaceGroupSharing.objects.get( workspace=upload_workspace.workspace, group=managed_group ) except WorkspaceGroupSharing.DoesNotExist: - self.needs_action.append( - ShareAsWriter( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_UPLOADERS_FUTURE_CYCLE, + current_sharing = None + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + if upload_cycle.is_future: + note = self.RC_UPLOADERS_FUTURE_CYCLE + if ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and not current_sharing.can_compute + ): + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) ) - ) - return - # Make sure sharing is correct. - if current_sharing.access == WorkspaceGroupSharing.WRITER: - if current_sharing.can_compute: + else: self.needs_action.append( ShareAsWriter( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_UPLOADERS_FUTURE_CYCLE, - current_sharing_instance=current_sharing, + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_current and not upload_cycle.is_ready_for_compute: + note = self.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE + if ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and not current_sharing.can_compute + ): + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, ) ) else: + self.needs_action.append( + ShareAsWriter( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_current and upload_cycle.is_ready_for_compute: + note = self.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE + if ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and current_sharing.can_compute + ): self.verified.append( VerifiedShared( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_UPLOADERS_FUTURE_CYCLE, - current_sharing_instance=current_sharing, + note=note, + **audit_result_args, ) ) - else: - self.needs_action.append( - ShareAsWriter( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_UPLOADERS_FUTURE_CYCLE, - current_sharing_instance=current_sharing, + else: + self.needs_action.append( + ShareWithCompute( + note=note, + **audit_result_args, + ) ) - ) diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 6d222565..9b7e5f08 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -46,11 +46,11 @@ class Params: ) is_current = Trait( start_date=timezone.localdate() - timedelta(days=45), - end_date=timezone.localdate() - timedelta(days=45), + end_date=timezone.localdate() + timedelta(days=45), ) is_future = Trait( - start_date=timezone.localdate() - timedelta(days=10), - end_date=timezone.localdate() - timedelta(days=100), + start_date=timezone.localdate() + timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=100), ) class Meta: diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 17d7a4f4..125f5523 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -188,7 +188,6 @@ def setUp(self): ) def test_uploaders_shared_as_writer_no_compute(self): - """audit method works with future upload cycle and group before compute is enabled.""" # Share the workspace with the group. sharing = WorkspaceGroupSharingFactory.create( workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER @@ -283,3 +282,214 @@ def test_gregor_all(self): def test_unexpected_group(self): pass + + +class UploadWorkspaceAuditCurrentCycleBeforeComputeTest(TestCase): + """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces before compute is enabled.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_current=True, + upload_cycle__is_ready_for_compute=False, + ) + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsWriter) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE + ) + + +class UploadWorkspaceAuditCurrentCycleAfterComputeTest(TestCase): + """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces after compute is enabled.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_current=True, upload_cycle__is_ready_for_compute=True + ) + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE + ) + + def test_other_groups(self): + pass From 6c8f63de380a8a3a50e3f08b91dff8b5f264077c Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 19 Aug 2024 15:22:03 -0700 Subject: [PATCH 016/125] Add auditing for RC uploader group for past cycle upload workspaces Note that this is before the combined workspace is shared; to add those checks, we'll need to add a flag on the upload cycle. --- .../audit/upload_workspace_audit.py | 40 ++- .../gregor_anvil/tests/test_audit.py | 244 +++++++++++++++++- 2 files changed, 280 insertions(+), 4 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 340e55f7..b35dca04 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -147,12 +147,16 @@ class Meta: class UploadWorkspaceAudit(GREGoRAudit): """A class to hold audit results for the GREGoR UploadWorkspace audit.""" - # Reasons for access/sharing. + # RC uploader statues. RC_UPLOADERS_FUTURE_CYCLE = "Uploaders should have write access for future cycles." RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE = ( "Uploaders should have write access before compute is enabled for this upload cycle." ) RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE = "Uploaders should have write access with compute for this upload cycle." + RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE = ( + "Uploaders should have read access to upload workspaces before QC is complete." + ) + RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE = "Upload group should not have direct access after QC is complete." results_table_class = UploadWorkspaceAuditTable @@ -280,3 +284,37 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group **audit_result_args, ) ) + elif upload_cycle.is_past and not upload_workspace.date_qc_completed: + note = self.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE + if current_sharing and current_sharing.access == WorkspaceGroupSharing.READER: + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareAsReader( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_past and upload_workspace.date_qc_completed: + note = self.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE + if not current_sharing: + self.verified.append( + VerifiedNotShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + StopSharing( + note=note, + **audit_result_args, + ) + ) + else: + raise ValueError("No case matched for RC uploader group.") diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 125f5523..e305fc7e 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -6,6 +6,7 @@ from anvil_consortium_manager.models import WorkspaceGroupSharing from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceGroupSharingFactory from django.test import TestCase +from django.utils import timezone from faker import Faker from ..audit import upload_workspace_audit @@ -177,7 +178,11 @@ def test_no_upload_workspaces(self): class UploadWorkspaceAuditFutureCycleTest(TestCase): - """Tests for the `UploadWorkspaceAudit` class for future cycle UploadWorkspaces.""" + """Tests for the `UploadWorkspaceAudit` class for future cycle UploadWorkspaces. + + Expectations at this point in the upload cycle: + - RC uploader group should be writers without compute. + """ def setUp(self): super().setUp() @@ -285,7 +290,11 @@ def test_unexpected_group(self): class UploadWorkspaceAuditCurrentCycleBeforeComputeTest(TestCase): - """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces before compute is enabled.""" + """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces before compute is enabled. + + Expectations at this point in the upload cycle: + - RC uploader group should be writers without compute. + """ def setUp(self): super().setUp() @@ -390,7 +399,11 @@ def test_uploaders_shared_as_owner(self): class UploadWorkspaceAuditCurrentCycleAfterComputeTest(TestCase): - """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces after compute is enabled.""" + """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces after compute is enabled. + + Expectations at this point in the upload cycle: + - RC uploader group should be writers with compute. + """ def setUp(self): super().setUp() @@ -493,3 +506,228 @@ def test_uploaders_shared_as_owner(self): def test_other_groups(self): pass + + +class UploadWorkspaceAuditPastCycleBeforeQCCompleteTest(TestCase): + """Tests for the `UploadWorkspaceAudit` class for past cycles before QC is complete. + + Expectations at this point in the upload cycle: + - RC uploader group should be readers. + """ + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + ) + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_other_groups(self): + pass + + +class UploadWorkspaceAuditPastCycleAfterQCCompleteTest(TestCase): + """Tests for the `UploadWorkspaceAudit` class for past cycles before QC is complete. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + date_qc_completed=fake.date_time_this_year( + before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() + ), + ) + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_other_groups(self): + pass From f20dc25c8037d1c5b9a1ed8ccc601c35196a589c Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 19 Aug 2024 15:51:36 -0700 Subject: [PATCH 017/125] Add a 'date_completed' field to the combined workspace model This field tracks if the data in the combined workspace is completed and ready to be shared with the consortium. --- ...iumcombineddataworkspace_date_completed.py | 23 +++++++++++++++++++ gregor_django/gregor_anvil/models.py | 6 +++++ 2 files changed, 29 insertions(+) create mode 100644 gregor_django/gregor_anvil/migrations/0029_consortiumcombineddataworkspace_date_completed.py diff --git a/gregor_django/gregor_anvil/migrations/0029_consortiumcombineddataworkspace_date_completed.py b/gregor_django/gregor_anvil/migrations/0029_consortiumcombineddataworkspace_date_completed.py new file mode 100644 index 00000000..1c200d87 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0029_consortiumcombineddataworkspace_date_completed.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2024-08-19 22:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gregor_anvil', '0028_uploadworkspace_date_qc_complete'), + ] + + operations = [ + migrations.AddField( + model_name='combinedconsortiumdataworkspace', + name='date_completed', + field=models.DateTimeField(blank=True, default=None, help_text='Date that data preparation in this workspace was completed.', null=True), + ), + migrations.AddField( + model_name='historicalcombinedconsortiumdataworkspace', + name='date_completed', + field=models.DateTimeField(blank=True, default=None, help_text='Date that data preparation in this workspace was completed.', null=True), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 02bf7ecb..ec09668b 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -279,6 +279,12 @@ class CombinedConsortiumDataWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track a workspace that has data combined from multiple upload workspaces.""" upload_cycle = models.ForeignKey(UploadCycle, on_delete=models.PROTECT) + date_completed = models.DateTimeField( + help_text="Date that data preparation in this workspace was completed.", + blank=True, + null=True, + default=None, + ) class ReleaseWorkspace(TimeStampedModel, BaseWorkspaceData): From a724dea09b936993160c1e9ddf505739bf2c123b Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 19 Aug 2024 16:30:06 -0700 Subject: [PATCH 018/125] Add auditing for RC uploaders after combined workspace is ready --- .../audit/upload_workspace_audit.py | 32 ++++- .../gregor_anvil/tests/test_audit.py | 121 ++++++++++++++++++ 2 files changed, 150 insertions(+), 3 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index b35dca04..3065179b 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -5,7 +5,7 @@ from django.conf import settings from django.db.models import Q, QuerySet -from ..models import UploadWorkspace +from ..models import CombinedConsortiumDataWorkspace, UploadWorkspace # from primed.primed_anvil.tables import BooleanIconColumn from .base import GREGoRAudit, GREGoRAuditResult @@ -156,7 +156,10 @@ class UploadWorkspaceAudit(GREGoRAudit): RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE = ( "Uploaders should have read access to upload workspaces before QC is complete." ) - RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE = "Upload group should not have direct access after QC is complete." + RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE = "Uploader group should not have direct access after QC is complete." + RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY = ( + "Uploader group should not have direct access when the combined workspace is ready to share or shared." + ) results_table_class = UploadWorkspaceAuditTable @@ -218,6 +221,13 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group except WorkspaceGroupSharing.DoesNotExist: current_sharing = None + try: + combined_workspace = CombinedConsortiumDataWorkspace.objects.get( + upload_cycle=upload_cycle, date_completed__isnull=False + ) + except CombinedConsortiumDataWorkspace.DoesNotExist: + combined_workspace = None + audit_result_args = { "workspace": upload_workspace, "managed_group": managed_group, @@ -300,7 +310,7 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group **audit_result_args, ) ) - elif upload_cycle.is_past and upload_workspace.date_qc_completed: + elif upload_cycle.is_past and upload_workspace.date_qc_completed and not combined_workspace: note = self.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE if not current_sharing: self.verified.append( @@ -316,5 +326,21 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group **audit_result_args, ) ) + elif upload_cycle.is_past and combined_workspace: + note = self.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + if not current_sharing: + self.verified.append( + VerifiedNotShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + StopSharing( + note=note, + **audit_result_args, + ) + ) else: raise ValueError("No case matched for RC uploader group.") diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index e305fc7e..53c143cd 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -731,3 +731,124 @@ def test_uploaders_shared_as_owner(self): def test_other_groups(self): pass + + +class UploadWorkspaceAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): + """Tests for the `UploadWorkspaceAudit` class for past cycles after the combined workspace is ready to share. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + date_qc_completed=fake.date_time_this_year( + before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() + ), + ) + # Create a corresponding combined workspace. + self.combined_workspace = factories.CombinedConsortiumDataWorkspaceFactory.create( + upload_cycle=self.upload_workspace.upload_cycle, + date_completed=fake.date_time_this_year( + before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() + ), + ) + + def test_uploaders_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_uploaders_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_uploaders_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.rc_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_uploaders_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_uploaders_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.rc_uploader_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_other_groups(self): + pass From 29c560f0c0fdf431aad1847560297263b2ecb3ec Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 19 Aug 2024 16:48:38 -0700 Subject: [PATCH 019/125] Add auditing for the DCC writer group --- .../audit/upload_workspace_audit.py | 150 ++++- .../gregor_anvil/tests/test_audit.py | 529 +++++++++++++++++- 2 files changed, 666 insertions(+), 13 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 3065179b..628b7eb5 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -161,6 +161,17 @@ class UploadWorkspaceAudit(GREGoRAudit): "Uploader group should not have direct access when the combined workspace is ready to share or shared." ) + # DCC writer group status. + DCC_WRITERS_FUTURE_CYCLE = "DCC writers should have write and compute access for future cycles." + DCC_WRITERS_CURRENT_CYCLE = "DCC writers should have write and compute access for the current upload cycle." + DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE = ( + "DCC writers should have write and compute access before QC is complete." + ) + DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE = "DCC writers should not have direct access after QC is complete." + DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY = ( + "DCC writers should not have direct access when the combined workspace is ready to share or shared." + ) + results_table_class = UploadWorkspaceAuditTable def __init__(self, queryset=None): @@ -202,31 +213,41 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): # Check the group type, and then call the appropriate audit method. if upload_workspace.research_center.uploader_group == managed_group: self._audit_workspace_and_rc_uploader_group(upload_workspace, managed_group) + elif managed_group.name == "GREGOR_DCC_WRITERS": + self._audit_workspace_and_dcc_writer_group(upload_workspace, managed_group) - def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group): - """Audit access for a specific UploadWorkspace and RC uploader group. - - Sharing expectations: - - Write access to future upload cycle. - - Write access before compute is enabled for current upload cycle. - - Write+compute access after compute is enabled for current upload cycle. - - Read access to past upload cycle workspaces before QC is completed. - - No access to past upload cycle workspaces after QC is completed (read access via auth domain). - """ - upload_cycle = upload_workspace.upload_cycle + def _get_current_sharing(self, upload_workspace, managed_group): try: current_sharing = WorkspaceGroupSharing.objects.get( workspace=upload_workspace.workspace, group=managed_group ) except WorkspaceGroupSharing.DoesNotExist: current_sharing = None + return current_sharing + def _get_combined_workspace(self, upload_cycle): + """Returns the combined workspace, but only if it is ready for sharing.""" try: combined_workspace = CombinedConsortiumDataWorkspace.objects.get( upload_cycle=upload_cycle, date_completed__isnull=False ) except CombinedConsortiumDataWorkspace.DoesNotExist: combined_workspace = None + return combined_workspace + + def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and RC uploader group. + + Sharing expectations: + - Write access to future upload cycle. + - Write access before compute is enabled for current upload cycle. + - Write+compute access after compute is enabled for current upload cycle. + - Read access to past upload cycle workspaces before QC is completed. + - No access to past upload cycle workspaces after QC is completed (read access via auth domain). + """ + upload_cycle = upload_workspace.upload_cycle + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + combined_workspace = self._get_combined_workspace(upload_cycle) audit_result_args = { "workspace": upload_workspace, @@ -344,3 +365,110 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group ) else: raise ValueError("No case matched for RC uploader group.") + + def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group): + upload_cycle = upload_workspace.upload_cycle + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + combined_workspace = self._get_combined_workspace(upload_cycle) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + if upload_cycle.is_future: + note = self.DCC_WRITERS_FUTURE_CYCLE + if ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and current_sharing.can_compute + ): + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareWithCompute( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_current: + note = self.DCC_WRITERS_CURRENT_CYCLE + if ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and current_sharing.can_compute + ): + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareWithCompute( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_past and not upload_workspace.date_qc_completed: + note = self.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE + if ( + current_sharing + and current_sharing.access == WorkspaceGroupSharing.WRITER + and current_sharing.can_compute + ): + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareWithCompute( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_past and upload_workspace.date_qc_completed and not combined_workspace: + note = self.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE + if not current_sharing: + self.verified.append( + VerifiedNotShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + StopSharing( + note=note, + **audit_result_args, + ) + ) + elif upload_cycle.is_past and combined_workspace: + note = self.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + if not current_sharing: + self.verified.append( + VerifiedNotShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + StopSharing( + note=note, + **audit_result_args, + ) + ) + + else: + raise ValueError("No case matched for DCC writer group.") diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 53c143cd..3b056cd5 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -182,11 +182,13 @@ class UploadWorkspaceAuditFutureCycleTest(TestCase): Expectations at this point in the upload cycle: - RC uploader group should be writers without compute. + - DCC writer group should be writers with compute. """ def setUp(self): super().setUp() self.rc_uploader_group = ManagedGroupFactory.create() + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) self.upload_workspace = factories.UploadWorkspaceFactory.create( research_center=self.research_center, upload_cycle__is_future=True @@ -273,8 +275,86 @@ def test_uploaders_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE) - def test_dcc_writers(self): - pass + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE) def test_dcc_admin(self): pass @@ -294,10 +374,12 @@ class UploadWorkspaceAuditCurrentCycleBeforeComputeTest(TestCase): Expectations at this point in the upload cycle: - RC uploader group should be writers without compute. + - DCC writer group should be writers with compute. """ def setUp(self): super().setUp() + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) self.upload_workspace = factories.UploadWorkspaceFactory.create( @@ -397,16 +479,99 @@ def test_uploaders_shared_as_owner(self): record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE ) + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + class UploadWorkspaceAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces after compute is enabled. Expectations at this point in the upload cycle: - RC uploader group should be writers with compute. + - DCC writer group should be writers with compute. """ def setUp(self): super().setUp() + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) self.upload_workspace = factories.UploadWorkspaceFactory.create( @@ -504,6 +669,87 @@ def test_uploaders_shared_as_owner(self): record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE ) + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + def test_other_groups(self): pass @@ -513,10 +759,12 @@ class UploadWorkspaceAuditPastCycleBeforeQCCompleteTest(TestCase): Expectations at this point in the upload cycle: - RC uploader group should be readers. + - DCC writer group should not have direct access. """ def setUp(self): super().setUp() + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) self.upload_workspace = factories.UploadWorkspaceFactory.create( @@ -615,6 +863,97 @@ def test_uploaders_shared_as_owner(self): record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE ) + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareWithCompute) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE + ) + def test_other_groups(self): pass @@ -624,10 +963,12 @@ class UploadWorkspaceAuditPastCycleAfterQCCompleteTest(TestCase): Expectations at this point in the upload cycle: - RC uploader group should not have direct access. + - DCC writer group should not have direct access. """ def setUp(self): super().setUp() + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) self.upload_workspace = factories.UploadWorkspaceFactory.create( @@ -729,6 +1070,97 @@ def test_uploaders_shared_as_owner(self): record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE ) + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE + ) + def test_other_groups(self): pass @@ -738,10 +1170,12 @@ class UploadWorkspaceAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): Expectations at this point in the upload cycle: - RC uploader group should not have direct access. + - DCC writer group should not have direct access. """ def setUp(self): super().setUp() + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) self.upload_workspace = factories.UploadWorkspaceFactory.create( @@ -850,5 +1284,96 @@ def test_uploaders_shared_as_owner(self): record.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY ) + def test_dcc_writers_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_dcc_writers_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_dcc_writers_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_dcc_writers_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + + def test_dcc_writers_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_writer_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual( + record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY + ) + def test_other_groups(self): pass From 3b9b01eae540559de2afab99991d8eddfe581352 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 19 Aug 2024 16:53:50 -0700 Subject: [PATCH 020/125] Change RC uploader to no direct sharing after QC is complete. They should still have read access by being in the members group in the auth domain. --- .../audit/upload_workspace_audit.py | 10 ++++---- .../gregor_anvil/tests/test_audit.py | 24 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 628b7eb5..2a0e3155 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -153,9 +153,7 @@ class UploadWorkspaceAudit(GREGoRAudit): "Uploaders should have write access before compute is enabled for this upload cycle." ) RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE = "Uploaders should have write access with compute for this upload cycle." - RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE = ( - "Uploaders should have read access to upload workspaces before QC is complete." - ) + RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE = "Uploaders should not have direct access before QC is complete." RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE = "Uploader group should not have direct access after QC is complete." RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY = ( "Uploader group should not have direct access when the combined workspace is ready to share or shared." @@ -317,16 +315,16 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group ) elif upload_cycle.is_past and not upload_workspace.date_qc_completed: note = self.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE - if current_sharing and current_sharing.access == WorkspaceGroupSharing.READER: + if not current_sharing: self.verified.append( - VerifiedShared( + VerifiedNotShared( note=note, **audit_result_args, ) ) else: self.needs_action.append( - ShareAsReader( + StopSharing( note=note, **audit_result_args, ) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 3b056cd5..c54b0da5 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -758,7 +758,7 @@ class UploadWorkspaceAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for past cycles before QC is complete. Expectations at this point in the upload cycle: - - RC uploader group should be readers. + - RC uploader group should not have direct access. - DCC writer group should not have direct access. """ @@ -783,7 +783,7 @@ def test_uploaders_shared_as_writer_no_compute(self): self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertIsInstance(record, upload_workspace_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) @@ -794,11 +794,11 @@ def test_uploaders_shared_as_writer_no_compute(self): def test_uploaders_not_shared(self): audit = upload_workspace_audit.UploadWorkspaceAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) - self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, None) @@ -819,7 +819,7 @@ def test_uploaders_shared_as_writer_can_compute(self): self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertIsInstance(record, upload_workspace_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) @@ -833,11 +833,11 @@ def test_uploaders_shared_as_reader(self): ) audit = upload_workspace_audit.UploadWorkspaceAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) - self.assertEqual(len(audit.verified), 1) - self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) - record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) @@ -855,7 +855,7 @@ def test_uploaders_shared_as_owner(self): self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertIsInstance(record, upload_workspace_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) From 449a241668cc62d6592528c0abe021fecec95741 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 19 Aug 2024 17:00:51 -0700 Subject: [PATCH 021/125] Automatically create an auth domain for UploadWorkspaceFactory --- gregor_django/gregor_anvil/tests/factories.py | 16 ++++++++++++++-- gregor_django/gregor_anvil/tests/test_views.py | 5 +---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 9b7e5f08..50d8a6af 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -1,8 +1,8 @@ from datetime import timedelta -from anvil_consortium_manager.tests.factories import WorkspaceFactory +from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceFactory from django.utils import timezone -from factory import Faker, LazyAttribute, Sequence, SubFactory, Trait +from factory import Faker, LazyAttribute, Sequence, SubFactory, Trait, post_generation from factory.django import DjangoModelFactory from .. import models @@ -79,6 +79,18 @@ class UploadWorkspaceFactory(DjangoModelFactory): class Meta: model = models.UploadWorkspace + skip_postgeneration_save = True + + @post_generation + def authorization_domains(self, create, extracted, **kwargs): + # Add an authorization domain. + if not create: + # Simple build, do nothing. + return + + # Create an authorization domain. + auth_domain = ManagedGroupFactory.create(name="auth_{}".format(self.workspace.name)) + self.workspace.authorization_domains.add(auth_domain) class PartnerUploadWorkspaceFactory(DjangoModelFactory): diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 478b7549..95d9d9b5 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1102,9 +1102,6 @@ def test_status_code(self): self.assertEqual(response.status_code, 200) def test_contains_share_with_auth_domain_button(self): - acm_factories.WorkspaceAuthorizationDomainFactory.create( - workspace=self.object.workspace, group__name="test_auth" - ) self.client.force_login(self.user) response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) url = reverse( @@ -1112,7 +1109,7 @@ def test_contains_share_with_auth_domain_button(self): args=[ self.object.workspace.billing_project.name, self.object.workspace.name, - "test_auth", + "auth_" + self.object.workspace.name, ], ) self.assertContains(response, url) From 04aab3b89335a917fba426fc66dfa89e3b03ce12 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 10:09:37 -0700 Subject: [PATCH 022/125] Add auditing for the UploadWorkspace auth domain sharing This one is easy - it should always be "read" access. --- .../audit/upload_workspace_audit.py | 42 ++ .../gregor_anvil/tests/test_audit.py | 498 +++++++++++++++++- 2 files changed, 531 insertions(+), 9 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 2a0e3155..c382cdcb 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -170,6 +170,9 @@ class UploadWorkspaceAudit(GREGoRAudit): "DCC writers should not have direct access when the combined workspace is ready to share or shared." ) + # Auth domain status. + AUTH_DOMAIN_AS_READER = "The auth domain should always be a reader." + results_table_class = UploadWorkspaceAuditTable def __init__(self, queryset=None): @@ -213,6 +216,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_rc_uploader_group(upload_workspace, managed_group) elif managed_group.name == "GREGOR_DCC_WRITERS": self._audit_workspace_and_dcc_writer_group(upload_workspace, managed_group) + elif managed_group in upload_workspace.workspace.authorization_domains.all(): + self._audit_workspace_and_auth_domain(upload_workspace, managed_group) def _get_current_sharing(self, upload_workspace, managed_group): try: @@ -365,6 +370,13 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group raise ValueError("No case matched for RC uploader group.") def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and the DCC writer group. + + Sharing expectations: + - Write+compute access to future and current upload cycles. + - Write+compute access to past upload cycles before QC is complete. + - No direct access to past upload cycle workspaces after QC is completed (read access via auth domain). + """ upload_cycle = upload_workspace.upload_cycle current_sharing = self._get_current_sharing(upload_workspace, managed_group) combined_workspace = self._get_combined_workspace(upload_cycle) @@ -470,3 +482,33 @@ def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group) else: raise ValueError("No case matched for DCC writer group.") + + def _audit_workspace_and_auth_domain(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and its auth domain. + + Sharing expectations: + - Read access at all times. + """ + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + note = self.AUTH_DOMAIN_AS_READER + if current_sharing and current_sharing.access == WorkspaceGroupSharing.READER: + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareAsReader( + note=note, + **audit_result_args, + ) + ) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index c54b0da5..15da81ef 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -193,6 +193,7 @@ def setUp(self): self.upload_workspace = factories.UploadWorkspaceFactory.create( research_center=self.research_center, upload_cycle__is_future=True ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -356,13 +357,94 @@ def test_dcc_writers_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE) + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + def test_dcc_admin(self): pass def test_anvil_groups(self): pass - def test_gregor_all(self): + def test_auth_domain(self): pass def test_unexpected_group(self): @@ -387,6 +469,7 @@ def setUp(self): upload_cycle__is_current=True, upload_cycle__is_ready_for_compute=False, ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -560,6 +643,87 @@ def test_dcc_writers_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + class UploadWorkspaceAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces after compute is enabled. @@ -577,6 +741,7 @@ def setUp(self): self.upload_workspace = factories.UploadWorkspaceFactory.create( research_center=self.research_center, upload_cycle__is_current=True, upload_cycle__is_ready_for_compute=True ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -750,8 +915,86 @@ def test_dcc_writers_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_CURRENT_CYCLE) - def test_other_groups(self): - pass + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) class UploadWorkspaceAuditPastCycleBeforeQCCompleteTest(TestCase): @@ -771,6 +1014,7 @@ def setUp(self): research_center=self.research_center, upload_cycle__is_past=True, ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -954,8 +1198,86 @@ def test_dcc_writers_shared_as_owner(self): record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE ) - def test_other_groups(self): - pass + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) class UploadWorkspaceAuditPastCycleAfterQCCompleteTest(TestCase): @@ -978,6 +1300,7 @@ def setUp(self): before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() ), ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -1161,8 +1484,86 @@ def test_dcc_writers_shared_as_owner(self): record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE ) - def test_other_groups(self): - pass + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) class UploadWorkspaceAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): @@ -1185,6 +1586,7 @@ def setUp(self): before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() ), ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() # Create a corresponding combined workspace. self.combined_workspace = factories.CombinedConsortiumDataWorkspaceFactory.create( upload_cycle=self.upload_workspace.upload_cycle, @@ -1375,5 +1777,83 @@ def test_dcc_writers_shared_as_owner(self): record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY ) - def test_other_groups(self): - pass + def test_auth_domain_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.auth_domain, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_auth_domain_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.auth_domain, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.auth_domain) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) From 9ab16937a7ce7bae1a5be92f8a8ddef1f4d05b9c Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 10:19:20 -0700 Subject: [PATCH 023/125] Add auditing for UploadWorkspaces and DCC admins group This is also easy - DCC admins should always be an owner. --- .../audit/upload_workspace_audit.py | 35 + .../gregor_anvil/tests/test_audit.py | 604 +++++++++++++++++- 2 files changed, 636 insertions(+), 3 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index c382cdcb..3f3966b4 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -170,6 +170,9 @@ class UploadWorkspaceAudit(GREGoRAudit): "DCC writers should not have direct access when the combined workspace is ready to share or shared." ) + # DCC admin group status. + DCC_ADMIN_AS_OWNER = "The DCC admin group should always be an owner." + # Auth domain status. AUTH_DOMAIN_AS_READER = "The auth domain should always be a reader." @@ -218,6 +221,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_dcc_writer_group(upload_workspace, managed_group) elif managed_group in upload_workspace.workspace.authorization_domains.all(): self._audit_workspace_and_auth_domain(upload_workspace, managed_group) + elif managed_group.name == settings.ANVIL_DCC_ADMINS_GROUP_NAME: + self._audit_workspace_and_dcc_admin_group(upload_workspace, managed_group) def _get_current_sharing(self, upload_workspace, managed_group): try: @@ -512,3 +517,33 @@ def _audit_workspace_and_auth_domain(self, upload_workspace, managed_group): **audit_result_args, ) ) + + def _audit_workspace_and_dcc_admin_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and the DCC admin group. + + Sharing expectations: + - Owner access at all times. + """ + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + note = self.DCC_ADMIN_AS_OWNER + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.verified.append( + VerifiedShared( + note=note, + **audit_result_args, + ) + ) + else: + self.needs_action.append( + ShareAsOwner( + note=note, + **audit_result_args, + ) + ) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 15da81ef..32e395d8 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -5,7 +5,8 @@ import django_tables2 as tables from anvil_consortium_manager.models import WorkspaceGroupSharing from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceGroupSharingFactory -from django.test import TestCase +from django.conf import settings +from django.test import TestCase, override_settings from django.utils import timezone from faker import Faker @@ -189,6 +190,7 @@ def setUp(self): super().setUp() self.rc_uploader_group = ManagedGroupFactory.create() self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) self.upload_workspace = factories.UploadWorkspaceFactory.create( research_center=self.research_center, upload_cycle__is_future=True @@ -438,8 +440,104 @@ def test_auth_domain_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) - def test_dcc_admin(self): - pass + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) def test_anvil_groups(self): pass @@ -461,6 +559,7 @@ class UploadWorkspaceAuditCurrentCycleBeforeComputeTest(TestCase): def setUp(self): super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) @@ -724,6 +823,105 @@ def test_auth_domain_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + class UploadWorkspaceAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces after compute is enabled. @@ -735,6 +933,7 @@ class UploadWorkspaceAuditCurrentCycleAfterComputeTest(TestCase): def setUp(self): super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) @@ -996,6 +1195,105 @@ def test_auth_domain_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + class UploadWorkspaceAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for past cycles before QC is complete. @@ -1007,6 +1305,7 @@ class UploadWorkspaceAuditPastCycleBeforeQCCompleteTest(TestCase): def setUp(self): super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) @@ -1279,6 +1578,105 @@ def test_auth_domain_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + class UploadWorkspaceAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for past cycles before QC is complete. @@ -1290,6 +1688,7 @@ class UploadWorkspaceAuditPastCycleAfterQCCompleteTest(TestCase): def setUp(self): super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) @@ -1565,6 +1964,105 @@ def test_auth_domain_shared_as_owner(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + class UploadWorkspaceAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for past cycles after the combined workspace is ready to share. @@ -1576,6 +2074,7 @@ class UploadWorkspaceAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): def setUp(self): super().setUp() + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) @@ -1857,3 +2356,102 @@ def test_auth_domain_shared_as_owner(self): self.assertEqual(record.managed_group, self.auth_domain) self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_dcc_admin_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.dcc_admin_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsOwner) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_dcc_admin_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.dcc_admin_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_dcc_admin_different_setting(self): + group = ManagedGroupFactory.create(name="foo") + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) From 31c46f581b787ce3c68d7efb29da7388c518fa28 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 10:29:20 -0700 Subject: [PATCH 024/125] Add auditing for UploadWorkspaces and other groups Any other group not covered in the audit should never have direct access. --- .../audit/upload_workspace_audit.py | 34 ++ .../gregor_anvil/tests/test_audit.py | 494 +++++++++++++++++- 2 files changed, 524 insertions(+), 4 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 3f3966b4..eb760e85 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -176,6 +176,9 @@ class UploadWorkspaceAudit(GREGoRAudit): # Auth domain status. AUTH_DOMAIN_AS_READER = "The auth domain should always be a reader." + # Other group. + OTHER_GROUP_NO_ACCESS = "Other groups should not have direct access." + results_table_class = UploadWorkspaceAuditTable def __init__(self, queryset=None): @@ -223,6 +226,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_auth_domain(upload_workspace, managed_group) elif managed_group.name == settings.ANVIL_DCC_ADMINS_GROUP_NAME: self._audit_workspace_and_dcc_admin_group(upload_workspace, managed_group) + else: + self._audit_workspace_and_other_group(upload_workspace, managed_group) def _get_current_sharing(self, upload_workspace, managed_group): try: @@ -547,3 +552,32 @@ def _audit_workspace_and_dcc_admin_group(self, upload_workspace, managed_group): **audit_result_args, ) ) + + def _audit_workspace_and_other_group(self, upload_workspace, managed_group): + """Audit access for a specific UploadWorkspace and other groups. + + Sharing expectations: + - No access. + """ + current_sharing = self._get_current_sharing(upload_workspace, managed_group) + + audit_result_args = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_sharing_instance": current_sharing, + } + + if not current_sharing: + self.verified.append( + VerifiedNotShared( + note=self.OTHER_GROUP_NO_ACCESS, + **audit_result_args, + ) + ) + else: + self.errors.append( + StopSharing( + note=self.OTHER_GROUP_NO_ACCESS, + **audit_result_args, + ) + ) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 32e395d8..9732d7ee 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -196,6 +196,7 @@ def setUp(self): research_center=self.research_center, upload_cycle__is_future=True ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -542,11 +543,86 @@ def test_dcc_admin_different_setting(self): def test_anvil_groups(self): pass - def test_auth_domain(self): - pass + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) - def test_unexpected_group(self): - pass + def test_other_group_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) class UploadWorkspaceAuditCurrentCycleBeforeComputeTest(TestCase): @@ -569,6 +645,7 @@ def setUp(self): upload_cycle__is_ready_for_compute=False, ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -922,6 +999,87 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + class UploadWorkspaceAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for current cycle UploadWorkspaces after compute is enabled. @@ -941,6 +1099,7 @@ def setUp(self): research_center=self.research_center, upload_cycle__is_current=True, upload_cycle__is_ready_for_compute=True ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -1294,6 +1453,87 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + class UploadWorkspaceAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for past cycles before QC is complete. @@ -1314,6 +1554,7 @@ def setUp(self): upload_cycle__is_past=True, ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -1677,6 +1918,87 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + class UploadWorkspaceAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for past cycles before QC is complete. @@ -1700,6 +2022,7 @@ def setUp(self): ), ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -2063,6 +2386,87 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + class UploadWorkspaceAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for past cycles after the combined workspace is ready to share. @@ -2093,6 +2497,7 @@ def setUp(self): before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() ), ) + self.other_group = ManagedGroupFactory.create() def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -2455,3 +2860,84 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.managed_group, group) self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_other_group_shared_as_writer_no_compute(self): + # Share the workspace with the group. + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_writer_can_compute(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.other_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_reader(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_other_group_shared_as_owner(self): + sharing = WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.other_group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) From ddc08c8ec25be5b376efb100e35edc75668d03ab Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 11:56:06 -0700 Subject: [PATCH 025/125] Reorder internal methods in UploadWorkspaceAudit --- .../audit/upload_workspace_audit.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index eb760e85..73118b6d 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -193,6 +193,25 @@ def _run_audit(self): for workspace in self.queryset: self.audit_upload_workspace(workspace) + def _get_current_sharing(self, upload_workspace, managed_group): + try: + current_sharing = WorkspaceGroupSharing.objects.get( + workspace=upload_workspace.workspace, group=managed_group + ) + except WorkspaceGroupSharing.DoesNotExist: + current_sharing = None + return current_sharing + + def _get_combined_workspace(self, upload_cycle): + """Returns the combined workspace, but only if it is ready for sharing.""" + try: + combined_workspace = CombinedConsortiumDataWorkspace.objects.get( + upload_cycle=upload_cycle, date_completed__isnull=False + ) + except CombinedConsortiumDataWorkspace.DoesNotExist: + combined_workspace = None + return combined_workspace + def audit_upload_workspace(self, upload_workspace): """Audit access for a specific UploadWorkspace.""" # Get a list of managed groups that should be included in this audit. @@ -229,25 +248,6 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): else: self._audit_workspace_and_other_group(upload_workspace, managed_group) - def _get_current_sharing(self, upload_workspace, managed_group): - try: - current_sharing = WorkspaceGroupSharing.objects.get( - workspace=upload_workspace.workspace, group=managed_group - ) - except WorkspaceGroupSharing.DoesNotExist: - current_sharing = None - return current_sharing - - def _get_combined_workspace(self, upload_cycle): - """Returns the combined workspace, but only if it is ready for sharing.""" - try: - combined_workspace = CombinedConsortiumDataWorkspace.objects.get( - upload_cycle=upload_cycle, date_completed__isnull=False - ) - except CombinedConsortiumDataWorkspace.DoesNotExist: - combined_workspace = None - return combined_workspace - def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group): """Audit access for a specific UploadWorkspace and RC uploader group. From e95f604770dbf4f3d093eee6bc0968685da12375 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 11:56:54 -0700 Subject: [PATCH 026/125] Add view to show UploadWorkspace audit results There are no tests for this view yet, since I'm not done writing the audit class itself. However, we can use this to check the audit against prod data. --- .../audit/upload_workspace_audit.py | 4 +- gregor_django/gregor_anvil/urls.py | 14 ++++ gregor_django/gregor_anvil/views.py | 18 +++++ gregor_django/templates/__audit_tables.html | 69 +++++++++++++++++++ .../gregor_anvil/upload_workspace_audit.html | 19 +++++ 5 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 gregor_django/templates/__audit_tables.html create mode 100644 gregor_django/templates/gregor_anvil/upload_workspace_audit.html diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 73118b6d..33cfb455 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -225,10 +225,10 @@ def audit_upload_workspace(self, upload_workspace): Q(name__in=["GREGOR_DCC_WRITERS", "GREGOR_ALL", settings.ANVIL_DCC_ADMINS_GROUP_NAME]) | # Auth domain. - Q(authorization_domains__workspace=upload_workspace.workspace) + Q(workspaceauthorizationdomain__workspace=upload_workspace.workspace) | # Groups that the workspace is shared with. - Q(workspace_group_sharing__workspace=upload_workspace.workspace) + Q(workspacegroupsharing__workspace=upload_workspace.workspace) ) for group in groups_to_audit: diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 771957d9..728ee128 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -45,6 +45,19 @@ "reports", ) +upload_workspace_audit_patterns = ( + [ + path("all/", views.UploadWorkspaceAuditAll.as_view(), name="all"), + ], + "upload_workspaces", +) +audit_patterns = ( + [ + path("upload_workspaces/", include(upload_workspace_audit_patterns)), + ], + "audit", +) + urlpatterns = [ # path("", views.Index.as_view(), name="index"), path("research_centers/", include(research_center_patterns)), @@ -52,4 +65,5 @@ path("consent_groups/", include(consent_group_patterns)), path("upload_cycles/", include(upload_cycle_patterns)), path("reports/", include(workspace_report_patterns)), + path("audit/", include(audit_patterns)), ] diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 4f251af5..6640c43d 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -12,6 +12,7 @@ from gregor_django.users.tables import UserTable from . import forms, models, tables +from .audit import upload_workspace_audit User = get_user_model() @@ -163,3 +164,20 @@ def get_context_data(self, **kwargs): ) context["workspace_count_table"] = tables.WorkspaceReportTable(qs) return context + + +class UploadWorkspaceAuditAll(AnVILConsortiumManagerStaffViewRequired, TemplateView): + """View to audit UploadWorkspace sharing.""" + + template_name = "gregor_anvil/upload_workspace_audit.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context diff --git a/gregor_django/templates/__audit_tables.html b/gregor_django/templates/__audit_tables.html new file mode 100644 index 00000000..c7107964 --- /dev/null +++ b/gregor_django/templates/__audit_tables.html @@ -0,0 +1,69 @@ +{% load django_tables2 %} + + +
    +
    +
    +

    + +

    +
    +
    + + {% render_table verified_table %} + +
    +
    +
    +
    +
    + + + +
    +
    +
    +

    + +

    +
    +
    + + {% render_table needs_action_table %} + +
    +
    +
    +
    +
    + + + +
    +
    +
    +

    + +

    +
    +
    + + {% render_table errors_table %} + +
    +
    +
    +
    +
    diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_audit.html b/gregor_django/templates/gregor_anvil/upload_workspace_audit.html new file mode 100644 index 00000000..edf4dd46 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/upload_workspace_audit.html @@ -0,0 +1,19 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load django_tables2 %} + +{% block title %}Upload workspace audit{% endblock %} + + +{% block content %} + +

    Upload workspace audit

    + +
    + Auditing sharing for all Upload workspaces +
    + +

    Audit results

    + +{% include "__audit_tables.html" with verified_table=verified_table needs_action_table=needs_action_table errors_table=errors_table %} + +{% endblock content %} From a35447da2b814ab06e840eb66310e647ef64b470 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 12:15:29 -0700 Subject: [PATCH 027/125] Add UploadWorkspace auditing for specific AnVIL groups The audit will just ignore these groups. --- .../audit/upload_workspace_audit.py | 18 +- .../gregor_anvil/tests/test_audit.py | 625 +++++++++++++++++- 2 files changed, 639 insertions(+), 4 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 33cfb455..14a72659 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -217,12 +217,18 @@ def audit_upload_workspace(self, upload_workspace): # Get a list of managed groups that should be included in this audit. # This includes the members group for the RC, the DCC groups, GREGOR_ALL, and the auth domain. research_center = upload_workspace.research_center + group_names_to_include = [ + "GREGOR_DCC_WRITERS", # DCC writers + settings.ANVIL_DCC_ADMINS_GROUP_NAME, # DCC admins + "anvil-admins", # AnVIL admins + "anvil_devs", # AnVIL devs + ] groups_to_audit = ManagedGroup.objects.filter( # RC uploader group. Q(research_center_of_uploaders=research_center) | - # Specific groups. - Q(name__in=["GREGOR_DCC_WRITERS", "GREGOR_ALL", settings.ANVIL_DCC_ADMINS_GROUP_NAME]) + # Specific groups from above. + Q(name__in=group_names_to_include) | # Auth domain. Q(workspaceauthorizationdomain__workspace=upload_workspace.workspace) @@ -245,9 +251,17 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_auth_domain(upload_workspace, managed_group) elif managed_group.name == settings.ANVIL_DCC_ADMINS_GROUP_NAME: self._audit_workspace_and_dcc_admin_group(upload_workspace, managed_group) + elif managed_group.name in ["anvil-admins", "anvil_devs"]: + self._audit_workspace_and_anvil_group(upload_workspace, managed_group) else: self._audit_workspace_and_other_group(upload_workspace, managed_group) + def _audit_workspace_and_anvil_group(self, upload_workspace, managed_group): + """Ignore the AnVIL groups in this audit. + + We don't want to make assumptions about what access level AnVIL has.""" + pass + def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group): """Audit access for a specific UploadWorkspace and RC uploader group. diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 9732d7ee..e8b45acd 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -197,6 +197,8 @@ def setUp(self): ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -540,8 +542,107 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) - def test_anvil_groups(self): - pass + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) def test_other_group_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -646,6 +747,8 @@ def setUp(self): ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -999,6 +1102,108 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + def test_other_group_shared_as_writer_no_compute(self): # Share the workspace with the group. sharing = WorkspaceGroupSharingFactory.create( @@ -1100,6 +1305,8 @@ def setUp(self): ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -1453,6 +1660,108 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + def test_other_group_shared_as_writer_no_compute(self): # Share the workspace with the group. sharing = WorkspaceGroupSharingFactory.create( @@ -1555,6 +1864,8 @@ def setUp(self): ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -1918,6 +2229,108 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + def test_other_group_shared_as_writer_no_compute(self): # Share the workspace with the group. sharing = WorkspaceGroupSharingFactory.create( @@ -2023,6 +2436,8 @@ def setUp(self): ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -2386,6 +2801,108 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + def test_other_group_shared_as_writer_no_compute(self): # Share the workspace with the group. sharing = WorkspaceGroupSharingFactory.create( @@ -2498,6 +3015,8 @@ def setUp(self): ), ) self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") def test_uploaders_shared_as_writer_no_compute(self): # Share the workspace with the group. @@ -2861,6 +3380,108 @@ def test_dcc_admin_different_setting(self): self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + def test_anvil_admins_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_admins, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_admins, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_no_compute(self): + # Share the workspace with the group. + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_shared(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_writer_can_compute(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.anvil_devs, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_reader(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_shared_as_owner(self): + WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=self.anvil_devs, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + def test_other_group_shared_as_writer_no_compute(self): # Share the workspace with the group. sharing = WorkspaceGroupSharingFactory.create( From ea5ddacc2b93f5a9ee53c2e045a16378d878aad9 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 12:19:42 -0700 Subject: [PATCH 028/125] Link non-member groups to their RC Add a foreign key in RC to the ManagedGroup model to link a non-member group (similar to what we've done for member and uploader groups). Show the non-member group on the detail page. --- .../0030_researchcenter_non_member_group.py | 25 +++++++++++++++ gregor_django/gregor_anvil/models.py | 13 ++++++++ .../gregor_anvil/tests/test_models.py | 32 +++++++++++++++++++ .../gregor_anvil/tests/test_views.py | 8 +++++ .../gregor_anvil/researchcenter_detail.html | 7 ++++ 5 files changed, 85 insertions(+) create mode 100644 gregor_django/gregor_anvil/migrations/0030_researchcenter_non_member_group.py diff --git a/gregor_django/gregor_anvil/migrations/0030_researchcenter_non_member_group.py b/gregor_django/gregor_anvil/migrations/0030_researchcenter_non_member_group.py new file mode 100644 index 00000000..6707a751 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0030_researchcenter_non_member_group.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.15 on 2024-08-20 19:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('anvil_consortium_manager', '0019_accountuserarchive'), + ('gregor_anvil', '0029_consortiumcombineddataworkspace_date_completed'), + ] + + operations = [ + migrations.AddField( + model_name='historicalresearchcenter', + name='non_member_group', + field=models.ForeignKey(blank=True, db_constraint=False, help_text='The AnVIL group containing non-members from this Research Center.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.managedgroup'), + ), + migrations.AddField( + model_name='researchcenter', + name='non_member_group', + field=models.OneToOneField(blank=True, help_text='The AnVIL group containing non-members from this Research Center.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='research_center_of_non_members', to='anvil_consortium_manager.managedgroup'), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index ec09668b..2387ab52 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -55,6 +55,15 @@ class ResearchCenter(TimeStampedModel, models.Model): null=True, ) + non_member_group = models.OneToOneField( + ManagedGroup, + on_delete=models.PROTECT, + help_text="The AnVIL group containing non-members from this Research Center.", + related_name="research_center_of_non_members", + blank=True, + null=True, + ) + uploader_group = models.OneToOneField( ManagedGroup, on_delete=models.PROTECT, @@ -83,6 +92,10 @@ def clean(self): # Members group and uploaders group must be different. if self.member_group and self.uploader_group and self.member_group == self.uploader_group: raise ValidationError("member_group and uploader_group must be different!") + if self.non_member_group and self.uploader_group and self.non_member_group == self.uploader_group: + raise ValidationError("non_member_group and uploader_group must be different!") + if self.member_group and self.non_member_group and self.member_group == self.non_member_group: + raise ValidationError("member_group and non_member_group must be different!") class PartnerGroup(TimeStampedModel, models.Model): diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index 95b79e55..022a490e 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -141,6 +141,38 @@ def test_member_group_uploader_group_must_be_different(self): self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) self.assertIn("must be different", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + def test_member_group_non_member_group_must_be_different(self): + """The same group cannot be used as the members group and non-members group.""" + group = ManagedGroupFactory.create() + instance = models.ResearchCenter( + full_name="Test name", + short_name="TEST", + member_group=group, + non_member_group=group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("must be different", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + + def test_non_member_group_uploader_group_must_be_different(self): + """The same group cannot be used as the members group and uploaders group.""" + group = ManagedGroupFactory.create() + instance = models.ResearchCenter( + full_name="Test name", + short_name="TEST", + non_member_group=group, + uploader_group=group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("must be different", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + def test_error_two_rcs_same_member_group(self): """Cannot have the same member group for two RCs.""" member_group = ManagedGroupFactory.create() diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 95d9d9b5..2a69c698 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -311,6 +311,14 @@ def test_link_to_member_group(self): response = self.client.get(self.get_url(obj.pk)) self.assertContains(response, member_group.get_absolute_url()) + def test_link_to_non_member_group(self): + """Response includes a link to the non-members group if it exists.""" + non_member_group = acm_factories.ManagedGroupFactory.create() + obj = self.model_factory.create(non_member_group=non_member_group) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + self.assertContains(response, non_member_group.get_absolute_url()) + def test_link_to_uploader_group(self): """Response includes a link to the uploader group if it exists.""" uploader_group = acm_factories.ManagedGroupFactory.create() diff --git a/gregor_django/templates/gregor_anvil/researchcenter_detail.html b/gregor_django/templates/gregor_anvil/researchcenter_detail.html index 4b8bb1ef..a149f17e 100644 --- a/gregor_django/templates/gregor_anvil/researchcenter_detail.html +++ b/gregor_django/templates/gregor_anvil/researchcenter_detail.html @@ -14,6 +14,13 @@ — {% endif %} +
  • Non-member group: + {% if object.non_member_group %} + {{ object.non_member_group }} + {% else %} + — + {% endif %} +
  • Uploader group: {% if object.uploader_group %} {{ object.uploader_group }} From cac5d2e838cb0e8e0db02e88638eef3635d7f1e8 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 12:35:19 -0700 Subject: [PATCH 029/125] Add tests for UploadWorkspaceAudit group selection These tests call the "run_audit" method instead of the method that audits a specific workspace and group. The run_audit flow handles selecting the proper groups. --- .../gregor_anvil/tests/test_audit.py | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index e8b45acd..ccf4cefc 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -177,6 +177,148 @@ def test_no_upload_workspaces(self): self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) + def test_one_upload_workspace_no_groups(self): + upload_workspace = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, upload_workspace.workspace.authorization_domains.first()) + self.assertEqual(record.current_sharing_instance, None) + self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_one_upload_workspace_rc_upload_group(self): + group = ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center__uploader_group=group, upload_cycle__is_future=True + ) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.WRITER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_writer_group(self): + group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.WRITER, can_compute=True + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_auth_domain(self): + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = upload_workspace.workspace.authorization_domains.first() + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) # auth domain is shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_admin_group(self): + group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_one_upload_workspace_dcc_admin_group_different_name(self): + group = ManagedGroupFactory.create(name="foo") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.OWNER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_anvil_admin_group(self): + group = ManagedGroupFactory.create(name="anvil-admins") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create(workspace=upload_workspace.workspace, group=group) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_anvil_dev_group(self): + group = ManagedGroupFactory.create(name="anvil_devs") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create(workspace=upload_workspace.workspace, group=group) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_other_group_shared(self): + group = ManagedGroupFactory.create(name="foo") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.READER + ) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_audit.StopSharing) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_other_group_not_shared(self): + ManagedGroupFactory.create(name="foo") + factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared + self.assertEqual(len(audit.errors), 0) + class UploadWorkspaceAuditFutureCycleTest(TestCase): """Tests for the `UploadWorkspaceAudit` class for future cycle UploadWorkspaces. From b22a23093331c33e619952bdeccd2534fb3c1c72 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 13:48:29 -0700 Subject: [PATCH 030/125] Add beginning views for auditing a single workspace and resolving audits Still no tests. --- gregor_django/gregor_anvil/urls.py | 11 ++ gregor_django/gregor_anvil/views.py | 177 +++++++++++++++++- .../gregor_anvil/upload_workspace_audit.html | 2 +- 3 files changed, 188 insertions(+), 2 deletions(-) diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 728ee128..9b0017ab 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -48,6 +48,17 @@ upload_workspace_audit_patterns = ( [ path("all/", views.UploadWorkspaceAuditAll.as_view(), name="all"), + path( + "resolve///", + views.UploadWorkspaceAuditResolve.as_view(), + name="resolve", + ), + path( + "//", + views.UploadWorkspaceAuditByWorkspace.as_view(), + name="upload_workspace", + ), + path("upload_cycle//", views.UploadWorkspaceAuditByUploadCycle.as_view(), name="upload_cycle"), ], "upload_workspaces", ) diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 6640c43d..1e18a8f9 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -1,11 +1,17 @@ +from anvil_consortium_manager.anvil_api import AnVILAPIError from anvil_consortium_manager.auth import ( AnVILConsortiumManagerStaffEditRequired, AnVILConsortiumManagerStaffViewRequired, ) -from anvil_consortium_manager.models import Account, Workspace +from anvil_consortium_manager.models import Account, ManagedGroup, Workspace, WorkspaceGroupSharing +from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.messages.views import SuccessMessageMixin +from django.db import transaction from django.db.models import Count, Q +from django.forms import Form +from django.http import Http404, HttpResponse +from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, DetailView, TemplateView from django_tables2 import MultiTableMixin, SingleTableView @@ -181,3 +187,172 @@ def get_context_data(self, **kwargs): context["needs_action_table"] = audit.get_needs_action_table() context["audit_results"] = audit return context + + +class UploadWorkspaceAuditByWorkspace(AnVILConsortiumManagerStaffViewRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_audit.html" + model = models.UploadWorkspace + + def get_object(self, queryset=None): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + billing_project_slug = self.kwargs.get("billing_project_slug", None) + workspace_slug = self.kwargs.get("workspace_slug", None) + queryset = models.UploadWorkspace.objects.filter( + workspace__billing_project__name=billing_project_slug, + workspace__name=workspace_slug, + ) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_audit.UploadWorkspaceAudit(queryset=self.model.objects.filter(pk=self.object.pk)) + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + +class UploadWorkspaceAuditByUploadCycle(AnVILConsortiumManagerStaffViewRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific upload cycle.""" + + template_name = "gregor_anvil/upload_workspace_audit.html" + model = models.UploadCycle + + def get_object(self, queryset=None): + """Look up the UploadCycle by cycle number.""" + try: + obj = models.UploadCycle.objects.get(cycle=self.kwargs.get("cycle", None)) + except models.UploadCycle.DoesNotExist: + raise Http404("No UploadCycle found matching the query") + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_audit.UploadWorkspaceAudit() + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + +class UploadWorkspaceAuditResolve(AnVILConsortiumManagerStaffEditRequired, TemplateView): + """View to resolve UploadWorkspace audit results.""" + + form_class = Form + template_name = "gregor_anvil/upload_workspace_audit_resolve.html" + htmx_success = """ Handled!""" + htmx_error = """ Error!""" + + def get_upload_workspace(self): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + billing_project_slug = self.kwargs.get("billing_project_slug", None) + workspace_slug = self.kwargs.get("workspace_slug", None) + queryset = models.UploadWorkspaceWorkspace.objects.filter( + workspace__billing_project__name=billing_project_slug, + workspace__name=workspace_slug, + ) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_managed_group(self, queryset=None): + """Look up the ManagedGroup by name.""" + try: + obj = ManagedGroup.objects.get(name=self.kwargs.get("managed_group_slug", None)) + except ManagedGroup.DoesNotExist: + raise Http404("No ManagedGroups found matching the query") + return obj + + def get_audit_result(self): + audit = upload_workspace_audit.UploadWorkspaceAudit() + # No way to set the group queryset, since it is dynamically determined by the workspace. + audit.audit_workspace_and_group(self.upload_workspace, self.managed_group) + # Set to completed, because we are just running this one specific check. + audit.completed = True + return audit.get_all_results()[0] + + def get(self, request, *args, **kwargs): + self.upload_workspace = self.get_upload_workspace() + self.managed_group = self.get_managed_group() + self.audit_result = self.get_audit_result() + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.upload_workspace = self.get_upload_workspace() + self.managed_group = self.get_managed_group() + self.audit_result = self.get_audit_result() + return super().post(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["upload_workspace"] = self.upload_workspace + context["managed_group"] = self.managed_group + context["audit_result"] = self.audit_result + return context + + def get_success_url(self): + return self.upload_workspace.get_absolute_url() + + def form_valid(self, form): + # Handle the result. + try: + if self.audit_result.current_sharing_instance: + sharing = self.audit_result.current_sharing_instance + else: + sharing = WorkspaceGroupSharing( + workspace=self.upload_workspace.workspace, + group=self.managed_group, + ) + with transaction.atomic(): + if isinstance(self.audit_result, upload_workspace_audit.StopSharing): + sharing.anvil_delete() + sharing.delete() + else: + if isinstance(self.audit_result, upload_workspace_audit.ShareAsReader): + sharing.access = WorkspaceGroupSharing.READER + sharing.can_compute = False + elif isinstance(self.audit_result, upload_workspace_audit.ShareAsWriter): + sharing.access = WorkspaceGroupSharing.WRITER + sharing.can_compute = False + elif isinstance(self.audit_result, upload_workspace_audit.ShareWithCompute): + sharing.access = WorkspaceGroupSharing.WRITER + sharing.can_compute = True + elif isinstance(self.audit_result, upload_workspace_audit.ShareAsOwner): + sharing.access = WorkspaceGroupSharing.OWNER + sharing.can_compute = True + sharing.full_clean() + sharing.save() + sharing.anvil_create_or_update() + except AnVILAPIError as e: + if self.request.htmx: + return HttpResponse(self.htmx_error) + else: + messages.error(self.request, "AnVIL API Error: " + str(e)) + return super().form_invalid(form) + # Otherwise, the audit resolution succeeded. + if self.request.htmx: + return HttpResponse(self.htmx_success) + else: + return super().form_valid(form) diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_audit.html b/gregor_django/templates/gregor_anvil/upload_workspace_audit.html index edf4dd46..9b3aa7a0 100644 --- a/gregor_django/templates/gregor_anvil/upload_workspace_audit.html +++ b/gregor_django/templates/gregor_anvil/upload_workspace_audit.html @@ -9,7 +9,7 @@

    Upload workspace audit

    - Auditing sharing for all Upload workspaces + Auditing sharing for {% if object %}{{ object }}{% else %}all upload workspaces{% endif %}.

    Audit results

    From f7f104337b97d54c490c49f136efc587a1301220 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 14:03:22 -0700 Subject: [PATCH 031/125] Add an action button to the audit results table Right now this button is just a link to a page that resolves the audit. Also add or make some modifications to templates (still not final). --- .../audit/upload_workspace_audit.py | 13 +++--- gregor_django/gregor_anvil/views.py | 2 +- .../upload_workspace_audit_action_button.html | 7 ++++ .../upload_workspace_audit_explanation.html | 1 + .../gregor_anvil/upload_workspace_audit.html | 2 + .../upload_workspace_audit_resolve.html | 42 +++++++++++++++++++ 6 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html create mode 100644 gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_explanation.html create mode 100644 gregor_django/templates/gregor_anvil/upload_workspace_audit_resolve.html diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 14a72659..e196d0c8 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -38,11 +38,11 @@ def get_table_dictionary(self): row = { "workspace": self.workspace, "managed_group": self.managed_group, - "is_shared": self.is_shared, + "access": self.current_sharing_instance.access if self.current_sharing_instance else None, + "can_compute": self.current_sharing_instance.can_compute if self.current_sharing_instance else None, "note": self.note, "action": self.action, "action_url": self.get_action_url(), - "current_sharing_instance": self.current_sharing_instance, } return row @@ -134,11 +134,12 @@ class UploadWorkspaceAuditTable(tables.Table): workspace = tables.Column(linkify=True) managed_group = tables.Column(linkify=True) - is_shared = tables.Column() + # is_shared = tables.Column() + access = tables.Column() + can_compute = tables.Column() note = tables.Column() - action = tables.Column() - current_sharing_instance = tables.Column(linkify=True) - # action = tables.TemplateColumn(template_name="gregor_anvil/snippets/upload_workspace_audit_action_button.html") + # action = tables.Column() + action = tables.TemplateColumn(template_name="gregor_anvil/snippets/upload_workspace_audit_action_button.html") class Meta: attrs = {"class": "table align-middle"} diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 1e18a8f9..80e8dde9 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -264,7 +264,7 @@ def get_upload_workspace(self): # Filter the queryset based on kwargs. billing_project_slug = self.kwargs.get("billing_project_slug", None) workspace_slug = self.kwargs.get("workspace_slug", None) - queryset = models.UploadWorkspaceWorkspace.objects.filter( + queryset = models.UploadWorkspace.objects.filter( workspace__billing_project__name=billing_project_slug, workspace__name=workspace_slug, ) diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html new file mode 100644 index 00000000..cb62c5ab --- /dev/null +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html @@ -0,0 +1,7 @@ +
    + {% if record.action %} + {{ record.action }} FOO + {% else %} + — + {% endif %} +
    diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_explanation.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_explanation.html new file mode 100644 index 00000000..dd6d86a4 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_explanation.html @@ -0,0 +1 @@ +XXX diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_audit.html b/gregor_django/templates/gregor_anvil/upload_workspace_audit.html index 9b3aa7a0..a772ea40 100644 --- a/gregor_django/templates/gregor_anvil/upload_workspace_audit.html +++ b/gregor_django/templates/gregor_anvil/upload_workspace_audit.html @@ -14,6 +14,8 @@

    Upload workspace audit

    Audit results

    +{% include "gregor_anvil/snippets/upload_workspace_audit_explanation.html" %} + {% include "__audit_tables.html" with verified_table=verified_table needs_action_table=needs_action_table errors_table=errors_table %} {% endblock content %} diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_audit_resolve.html b/gregor_django/templates/gregor_anvil/upload_workspace_audit_resolve.html new file mode 100644 index 00000000..7ee9c159 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/upload_workspace_audit_resolve.html @@ -0,0 +1,42 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load django_tables2 %} +{% load crispy_forms_tags %} + +{% block title %}Resolve upload workspace audit{% endblock %} + + +{% block content %} + +

    Resolve upload workspace audit

    + +
    + +
    + +

    Audit results

    + +{% include "gregor_anvil/snippets/upload_workspace_audit_explanation.html" %} + +
    +
    +
    Result
    +

    {{ audit_result }}

    + {% if audit_result.action %} +
    + + {% csrf_token %} + {{ form|crispy }} + + +
    + {% else %} + + {% endif %} +
    +
    + + +{% endblock content %} From 6edb50e566c492fb100b4040ab75a5a0a3f79ed9 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 14:37:37 -0700 Subject: [PATCH 032/125] Add a script to add test data for upload workspace auditing --- add_upload_workspace_audit_test_data.py | 102 ++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 add_upload_workspace_audit_test_data.py diff --git a/add_upload_workspace_audit_test_data.py b/add_upload_workspace_audit_test_data.py new file mode 100644 index 00000000..51ce1df1 --- /dev/null +++ b/add_upload_workspace_audit_test_data.py @@ -0,0 +1,102 @@ +from anvil_consortium_manager.tests.factories import ( + ManagedGroupFactory, +) +from django.conf import settings +from django.utils import timezone + +from gregor_django.gregor_anvil.tests import factories + +# Create groups involved in the audit. +dcc_admin_group = ManagedGroupFactory(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) +dcc_writer_group = ManagedGroupFactory(name="GREGOR_DCC_WRITERS") +rc_1_member_group = ManagedGroupFactory(name="DEMO_RC1_MEMBERS") +rc_1_uploader_group = ManagedGroupFactory(name="DMEO_RC1_UPLOADERS") + +# Create an RC +rc = factories.ResearchCenterFactory.create( + full_name="Research Center 1", + short_name="RC1", + member_group=rc_1_member_group, + uploader_group=rc_1_uploader_group, +) + + +# Create a future upload cycle. +upload_cycle = factories.UploadCycleFactory.create( + cycle=1, + is_future=True, + is_ready_for_compute=False, +) +factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U01_RC1", +) + +# Create a current upload cycle before compute. +upload_cycle = factories.UploadCycleFactory.create( + cycle=2, + is_current=True, + is_ready_for_compute=False, +) +factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U02_RC1", +) + +# Create a current upload cycle before compute. +upload_cycle = factories.UploadCycleFactory.create( + cycle=3, + is_current=True, + is_ready_for_compute=True, +) +factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U03_RC1", +) + +# Create a past upload cycle before QC is completed. +upload_cycle = factories.UploadCycleFactory.create( + cycle=4, + is_current=True, + is_ready_for_compute=True, +) +factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U04_RC1", + date_qc_completed=None, +) + +# Create a past upload cycle after QC is completed. +upload_cycle = factories.UploadCycleFactory.create( + cycle=5, + is_current=True, + is_ready_for_compute=True, +) +factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U05_RC1", + date_qc_completed=timezone.now(), +) + +# Create a past upload cycle with a combined workspace. +upload_cycle = factories.UploadCycleFactory.create( + cycle=6, + is_past=True, + is_ready_for_compute=True, +) +factories.UploadWorkspaceFactory.create( + upload_cycle=upload_cycle, + research_center=rc, + workspace__name="TEST_U06_RC1", + date_qc_completed=timezone.now(), +) +factories.CombinedConsortiumDataWorkspaceFactory.create( + upload_cycle=upload_cycle, + date_completed=timezone.now(), + workspace__name="TEST_U06_COMBINED", +) From dd7483fa174d835716936ca3a957c9b7883f7782 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 14:43:37 -0700 Subject: [PATCH 033/125] Add an "audit consortium access" button to the UploadWorkspaceDetail page This button links to the audit view for this workspace. It also replaces the current "share with auth domain" button that used to be on the page, because sharing with the auth domain will now happen via the audit. --- gregor_django/gregor_anvil/tests/test_views.py | 5 ++--- .../templates/gregor_anvil/uploadworkspace_detail.html | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 2a69c698..dbf9015f 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1109,15 +1109,14 @@ def test_status_code(self): response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) self.assertEqual(response.status_code, 200) - def test_contains_share_with_auth_domain_button(self): + def test_contains_audit_consortium_access_button(self): self.client.force_login(self.user) response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) url = reverse( - "anvil_consortium_manager:workspaces:sharing:new_by_group", + "gregor_anvil:audit:upload_workspaces:upload_workspace", args=[ self.object.workspace.billing_project.name, self.object.workspace.name, - "auth_" + self.object.workspace.name, ], ) self.assertContains(response, url) diff --git a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html index 5bc071a2..f682cc8c 100644 --- a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html @@ -40,13 +40,11 @@

    {% block action_buttons %} {% if show_edit_links %} - {% if object.authorization_domains.first %}

    - Share with auth domain -

    - {% else %} -

    no auth domain

    - {% endif %} + + Audit consortium sharing + +

    {% endif %} {{ block.super }} From 977d4af8afbbcf65ddbfc3c2ebee0fe06decd869 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 14:52:49 -0700 Subject: [PATCH 034/125] Add a placeholder button for auditing auth domain membership The code to audit auth domain membership for an upload workspace does not yet exist, but it will. For now, add a button to the workspace detail page without actually linking to a view (since it doesn't exist yet). --- .../templates/gregor_anvil/uploadworkspace_detail.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html index f682cc8c..7f8cb82b 100644 --- a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html @@ -44,6 +44,9 @@

    Audit consortium sharing + + Audit auth domain membership +

    {% endif %} From f7fe3f6ba6ce001416466ea2322dff4e8e75b6c3 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 15:00:00 -0700 Subject: [PATCH 035/125] Only show workspace name in str method --- gregor_django/gregor_anvil/models.py | 3 +++ gregor_django/gregor_anvil/tests/test_models.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 2387ab52..64a26764 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -246,6 +246,9 @@ class Meta: ), ] + def __str__(self): + return self.workspace.name + class PartnerUploadWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track additional data about a partner workspace.""" diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index 022a490e..f9883337 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -493,7 +493,7 @@ def test_str_method(self): instance = factories.UploadWorkspaceFactory.create() instance.save() self.assertIsInstance(instance.__str__(), str) - self.assertEqual(instance.__str__(), instance.workspace.__str__()) + self.assertEqual(instance.__str__(), instance.workspace.name) def test_unique_constraint(self): """Cannot save two instances with the same ResearchCenter, ConsentGroup, and version.""" From 20dfe35c99a6a988ab746cd480c50baa0415b893 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 20 Aug 2024 16:25:11 -0700 Subject: [PATCH 036/125] Temporarily remove a couple of audit views We'll work on testing the view for auditing a single workspace and the view for resolving an audit, and then go back and add other views (with comprehensive tests). --- gregor_django/gregor_anvil/urls.py | 4 +-- gregor_django/gregor_anvil/views.py | 43 ----------------------------- 2 files changed, 2 insertions(+), 45 deletions(-) diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 9b0017ab..6824756f 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -47,7 +47,7 @@ upload_workspace_audit_patterns = ( [ - path("all/", views.UploadWorkspaceAuditAll.as_view(), name="all"), + # path("all/", views.UploadWorkspaceAuditAll.as_view(), name="all"), path( "resolve///", views.UploadWorkspaceAuditResolve.as_view(), @@ -58,7 +58,7 @@ views.UploadWorkspaceAuditByWorkspace.as_view(), name="upload_workspace", ), - path("upload_cycle//", views.UploadWorkspaceAuditByUploadCycle.as_view(), name="upload_cycle"), + # path("upload_cycle//", views.UploadWorkspaceAuditByUploadCycle.as_view(), name="upload_cycle"), ], "upload_workspaces", ) diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 80e8dde9..c70b93c6 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -172,23 +172,6 @@ def get_context_data(self, **kwargs): return context -class UploadWorkspaceAuditAll(AnVILConsortiumManagerStaffViewRequired, TemplateView): - """View to audit UploadWorkspace sharing.""" - - template_name = "gregor_anvil/upload_workspace_audit.html" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - # Run the audit. - audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.run_audit() - context["verified_table"] = audit.get_verified_table() - context["errors_table"] = audit.get_errors_table() - context["needs_action_table"] = audit.get_needs_action_table() - context["audit_results"] = audit - return context - - class UploadWorkspaceAuditByWorkspace(AnVILConsortiumManagerStaffViewRequired, DetailView): """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" @@ -225,32 +208,6 @@ def get_context_data(self, **kwargs): return context -class UploadWorkspaceAuditByUploadCycle(AnVILConsortiumManagerStaffViewRequired, DetailView): - """View to audit UploadWorkspace sharing for a specific upload cycle.""" - - template_name = "gregor_anvil/upload_workspace_audit.html" - model = models.UploadCycle - - def get_object(self, queryset=None): - """Look up the UploadCycle by cycle number.""" - try: - obj = models.UploadCycle.objects.get(cycle=self.kwargs.get("cycle", None)) - except models.UploadCycle.DoesNotExist: - raise Http404("No UploadCycle found matching the query") - return obj - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - # Run the audit. - audit = upload_workspace_audit.UploadWorkspaceAudit() - audit.run_audit() - context["verified_table"] = audit.get_verified_table() - context["errors_table"] = audit.get_errors_table() - context["needs_action_table"] = audit.get_needs_action_table() - context["audit_results"] = audit - return context - - class UploadWorkspaceAuditResolve(AnVILConsortiumManagerStaffEditRequired, TemplateView): """View to resolve UploadWorkspace audit results.""" From 22466b54f593e811ddecbbb5e08152e114a0da1d Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 21 Aug 2024 10:23:31 -0700 Subject: [PATCH 037/125] Add django-htmx to requirements file --- requirements/requirements.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements/requirements.in b/requirements/requirements.in index 4de5165b..313b7167 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -55,3 +55,6 @@ jsonapi-requests # for exporting django-tables2 tables to tsv. tablib + +# for htmx +django-htmx From 161b06ae4229cd852d1d524cf11b98c8ac5f6189 Mon Sep 17 00:00:00 2001 From: amstilp <3944584+amstilp@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:24:18 +0000 Subject: [PATCH 038/125] Compile requirements files --- requirements/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 1639725f..5e05c55f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -12,6 +12,7 @@ asgiref==3.8.1 # via # -r requirements/requirements.in # django + # django-htmx build==1.0.3 # via pip-tools cachetools==5.3.2 @@ -45,6 +46,7 @@ django==4.2.15 # django-dbbackup # django-extensions # django-filter + # django-htmx # django-model-utils # django-picklefield # django-simple-history @@ -72,6 +74,8 @@ django-extensions==3.2.1 # django-anvil-consortium-manager django-filter==23.5 # via django-anvil-consortium-manager +django-htmx==1.19.0 + # via -r requirements/requirements.in django-login-required-middleware==0.9.0 # via -r requirements/requirements.in django-maintenance-mode==0.21.1 From 4a11da2925a95aaee63f500a5a2969742140c46c Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 21 Aug 2024 11:30:29 -0700 Subject: [PATCH 039/125] Install the django-htmx package in the project --- config/settings/base.py | 2 ++ gregor_django/templates/base.html | 1 + 2 files changed, 3 insertions(+) diff --git a/config/settings/base.py b/config/settings/base.py index 55cccf80..5539f254 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -86,6 +86,7 @@ "fontawesomefree", "simple_history", "constance", + "django_htmx", ] LOCAL_APPS = [ @@ -153,6 +154,7 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", "maintenance_mode.middleware.MaintenanceModeMiddleware", "simple_history.middleware.HistoryRequestMiddleware", + "django_htmx.middleware.HtmxMiddleware", ] # STATIC diff --git a/gregor_django/templates/base.html b/gregor_django/templates/base.html index 94e4a3e5..4a6a5c08 100644 --- a/gregor_django/templates/base.html +++ b/gregor_django/templates/base.html @@ -30,6 +30,7 @@ + From 680f57e261ea2430c180f4d62fd4e37c16fd6069 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 21 Aug 2024 14:26:00 -0700 Subject: [PATCH 040/125] Add freezegun to requirements file Needed for testing timestamps --- requirements/test-requirements.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements/test-requirements.in b/requirements/test-requirements.in index 79f587cf..9381429e 100644 --- a/requirements/test-requirements.in +++ b/requirements/test-requirements.in @@ -25,3 +25,5 @@ pytest-django # https://github.com/pytest-dev/pytest-django django_test_migrations # Mock json api data marshmallow-jsonapi +# Freeze time in tests +freezegun # https://github.com/spulec/freezegun From e2e575c1763579f3ea824613cdf9d5ca6e1f0ea7 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 21 Aug 2024 14:26:19 -0700 Subject: [PATCH 041/125] Add tests for UploadWorkspaceAuditResolve --- .../gregor_anvil/tests/test_views.py | 1072 +++++++++++++++++ gregor_django/gregor_anvil/views.py | 45 +- 2 files changed, 1099 insertions(+), 18 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index dbf9015f..a5770b52 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -6,6 +6,7 @@ from anvil_consortium_manager import models as acm_models from anvil_consortium_manager.models import AnVILProjectManagerAccess from anvil_consortium_manager.tests import factories as acm_factories +from anvil_consortium_manager.tests.api_factories import ErrorResponseFactory from anvil_consortium_manager.tests.utils import AnVILAPIMockTestMixin from constance.test import override_config from django.conf import settings @@ -17,11 +18,14 @@ from django.shortcuts import resolve_url from django.test import RequestFactory, TestCase from django.urls import reverse +from django.utils import timezone +from freezegun import freeze_time from gregor_django.users.tables import UserTable from gregor_django.users.tests.factories import UserFactory from .. import forms, models, tables, views +from ..audit import upload_workspace_audit from . import factories # from .utils import AnVILAPIMockTestMixin @@ -2144,3 +2148,1071 @@ def test_cc_admins_membership(self): self.assertEqual(membership.parent_group, new_group) self.assertEqual(membership.child_group, self.admins_group) self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + +class UploadWorkspaceAuditByWorkspaceTest(AnVILAPIMockTestMixin, TestCase): + def test_tests(self): + self.fail("write tests") + + +class UploadWorkspaceAuditResolveTest(AnVILAPIMockTestMixin, TestCase): + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:resolve", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuditResolve.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url("foo", "bar", "foobar")) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar", "foobar"), + ) + + def test_status_code_with_user_permission_staff_edit(self): + """Returns successful response code if the user has staff edit permission.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + + def test_status_code_with_user_permission_staff_view(self): + """Returns 403 response code if the user has staff view permission.""" + user_view = User.objects.create_user(username="test-view", password="test-view") + user_view.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(self.user) + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user_view + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_status_code_with_user_permission_view(self): + """Returns forbidden response code if the user has view permission.""" + user = User.objects.create_user(username="test-none", password="test-none") + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_get_upload_workspace_does_not_exist(self): + """Raises a 404 error with an invalid object dbgap_application_pk.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url("foo", "bar", "foobar")) + self.assertEqual(response.status_code, 404) + + def test_get_group_does_not_exist(self): + """get request raises a 404 error with an non-existent email.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + "foo", + ) + ) + self.assertEqual(response.status_code, 404) + + def test_get_context_audit_result(self): + """The data_access_audit exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + self.assertIsInstance( + response.context_data["audit_result"], + upload_workspace_audit.UploadWorkspaceAuditResult, + ) + + def test_get_verified_shared(self): + """Get request with VerifiedShared result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = upload_workspace.workspace.authorization_domains.first() + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.VerifiedShared) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual(audit_result.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_get_verified_not_shared(self): + """Get request with VerifiedNotShared result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.VerifiedNotShared) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual(audit_result.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + def test_get_share_as_reader(self): + """Get request with ShareAsReader result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = upload_workspace.workspace.authorization_domains.first() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.ShareAsReader) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual(audit_result.note, upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER) + + def test_get_share_as_writer(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, + research_center__uploader_group=group, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.ShareAsWriter) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual(audit_result.note, upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE) + + def test_get_share_with_compute(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.ShareWithCompute) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual(audit_result.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE) + + def test_get_share_as_owner(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.ShareAsOwner) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual(audit_result.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) + + def test_post_upload_workspace_does_not_exist(self): + """Raises a 404 error with an invalid object dbgap_application_pk.""" + self.client.force_login(self.user) + response = self.client.post(self.get_url("foo", "bar", "foobar")) + self.assertEqual(response.status_code, 404) + + def test_post_group_does_not_exist(self): + """post request raises a 404 error with an non-existent email.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.post( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + "foo", + ) + ) + self.assertEqual(response.status_code, 404) + + def test_post_verified_shared(self): + """Post request with VerifiedShared result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = upload_workspace.workspace.authorization_domains.first() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertEqual(sharing.modified, date_created) + + def test_post_verified_not_shared(self): + """Post request with VerifiedNotShared result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + + def test_post_new_share_as_reader(self): + """Post request with ShareAsReader result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "READER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.READER) + self.assertFalse(sharing.can_compute) + + def test_post_new_share_as_writer(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + research_center__uploader_group=group, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertFalse(sharing.can_compute) + + def test_post_new_share_with_compute(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertTrue(sharing.can_compute) + + def test_post_new_share_as_owner(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "OWNER", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.OWNER) + self.assertTrue(sharing.can_compute) + + def test_post_new_stop_sharing(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "NO ACCESS", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + + def test_post_update_share_as_reader(self): + """Post request with ShareAsReader result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.WRITER, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "READER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertGreater(sharing.modified, date_created) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.READER) + self.assertFalse(sharing.can_compute) + + def test_post_update_share_as_writer(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + research_center__uploader_group=group, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertGreater(sharing.modified, date_created) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertFalse(sharing.can_compute) + + def test_post_update_share_with_compute(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertGreater(sharing.modified, date_created) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertTrue(sharing.can_compute) + + def test_post_update_share_as_owner(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "OWNER", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertGreater(sharing.modified, date_created) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.OWNER) + self.assertTrue(sharing.can_compute) + + def test_post_share_as_reader_htmx(self): + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "READER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.READER) + self.assertFalse(sharing.can_compute) + + def test_post_new_share_as_writer_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + research_center__uploader_group=group, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertFalse(sharing.can_compute) + + def test_post_new_share_with_compute_htmx(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.WRITER) + self.assertTrue(sharing.can_compute) + + def test_post_new_share_as_owner_htmx(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "OWNER", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing = acm_models.WorkspaceGroupSharing.objects.get(workspace=upload_workspace.workspace, group=group) + self.assertEqual(sharing.access, acm_models.WorkspaceGroupSharing.OWNER) + self.assertTrue(sharing.can_compute) + + def test_post_new_stop_sharing_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + ) + # Add the mocked API response. + acls = [ + { + "email": group.email, + "accessLevel": "NO ACCESS", + "canShare": False, # We're not tracking this in ACM so we always send False. + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_success) + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + + def test_post_share_as_reader_anvil_api_error(self): + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.ShareAsReader) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_new_share_as_writer_anvil_api_error(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + research_center__uploader_group=group, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.ShareAsWriter) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_new_share_with_compute_anvil_api_error(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.ShareWithCompute) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_new_share_as_owner_anvil_api_error(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.ShareAsOwner) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_new_stop_sharing_anvil_api_error(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The sharing object was not deleted. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertEqual(sharing.modified, date_created) + # Audit result is still as expected. + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_audit.StopSharing) + # A message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_share_as_reader_anvil_api_error_htmx(self): + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + group = upload_workspace.workspace.authorization_domains.first() + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_error) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_share_as_writer_anvil_api_error_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + research_center__uploader_group=group, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_error) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_share_with_compute_anvil_api_error_htmx(self): + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_error) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_share_as_owner_anvil_api_error_htmx(self): + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_error) + # No sharing object was created. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_new_stop_sharing_anvil_api_error_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + ) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + sharing = acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + ) + # Add the mocked API response. + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_error) + # The sharing object was not deleted. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 1) + sharing.refresh_from_db() + self.assertEqual(sharing.created, date_created) + self.assertEqual(sharing.modified, date_created) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index c70b93c6..001aeb25 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -12,7 +12,7 @@ from django.forms import Form from django.http import Http404, HttpResponse from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, DetailView, TemplateView +from django.views.generic import CreateView, DetailView, FormView, TemplateView from django_tables2 import MultiTableMixin, SingleTableView from gregor_django.users.tables import UserTable @@ -208,7 +208,7 @@ def get_context_data(self, **kwargs): return context -class UploadWorkspaceAuditResolve(AnVILConsortiumManagerStaffEditRequired, TemplateView): +class UploadWorkspaceAuditResolve(AnVILConsortiumManagerStaffEditRequired, FormView): """View to resolve UploadWorkspace audit results.""" form_class = Form @@ -275,6 +275,7 @@ def get_success_url(self): def form_valid(self, form): # Handle the result. try: + # Set up the sharing instance. if self.audit_result.current_sharing_instance: sharing = self.audit_result.current_sharing_instance else: @@ -283,25 +284,33 @@ def form_valid(self, form): group=self.managed_group, ) with transaction.atomic(): - if isinstance(self.audit_result, upload_workspace_audit.StopSharing): - sharing.anvil_delete() - sharing.delete() - else: - if isinstance(self.audit_result, upload_workspace_audit.ShareAsReader): - sharing.access = WorkspaceGroupSharing.READER - sharing.can_compute = False - elif isinstance(self.audit_result, upload_workspace_audit.ShareAsWriter): - sharing.access = WorkspaceGroupSharing.WRITER - sharing.can_compute = False - elif isinstance(self.audit_result, upload_workspace_audit.ShareWithCompute): - sharing.access = WorkspaceGroupSharing.WRITER - sharing.can_compute = True - elif isinstance(self.audit_result, upload_workspace_audit.ShareAsOwner): - sharing.access = WorkspaceGroupSharing.OWNER - sharing.can_compute = True + if isinstance(self.audit_result, upload_workspace_audit.ShareAsReader): + sharing.access = WorkspaceGroupSharing.READER + sharing.can_compute = False sharing.full_clean() sharing.save() sharing.anvil_create_or_update() + elif isinstance(self.audit_result, upload_workspace_audit.ShareAsWriter): + sharing.access = WorkspaceGroupSharing.WRITER + sharing.can_compute = False + sharing.full_clean() + sharing.save() + sharing.anvil_create_or_update() + elif isinstance(self.audit_result, upload_workspace_audit.ShareWithCompute): + sharing.access = WorkspaceGroupSharing.WRITER + sharing.can_compute = True + sharing.full_clean() + sharing.save() + sharing.anvil_create_or_update() + elif isinstance(self.audit_result, upload_workspace_audit.ShareAsOwner): + sharing.access = WorkspaceGroupSharing.OWNER + sharing.can_compute = True + sharing.full_clean() + sharing.save() + sharing.anvil_create_or_update() + elif isinstance(self.audit_result, upload_workspace_audit.StopSharing): + sharing.anvil_delete() + sharing.delete() except AnVILAPIError as e: if self.request.htmx: return HttpResponse(self.htmx_error) From 58611f6f65f40f5312b070a3a64903fe561ea7cc Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 21 Aug 2024 14:28:26 -0700 Subject: [PATCH 042/125] Streamline code in UploadWorkspaceAuditResolve view Instead of calling full_clean, save, and anvil_create_or_update in each if/elif statement, rearrange the if statement block so we can just call them once. --- gregor_django/gregor_anvil/views.py | 46 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 001aeb25..f13dc6df 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -284,33 +284,31 @@ def form_valid(self, form): group=self.managed_group, ) with transaction.atomic(): - if isinstance(self.audit_result, upload_workspace_audit.ShareAsReader): - sharing.access = WorkspaceGroupSharing.READER - sharing.can_compute = False - sharing.full_clean() - sharing.save() - sharing.anvil_create_or_update() - elif isinstance(self.audit_result, upload_workspace_audit.ShareAsWriter): - sharing.access = WorkspaceGroupSharing.WRITER - sharing.can_compute = False - sharing.full_clean() - sharing.save() - sharing.anvil_create_or_update() - elif isinstance(self.audit_result, upload_workspace_audit.ShareWithCompute): - sharing.access = WorkspaceGroupSharing.WRITER - sharing.can_compute = True - sharing.full_clean() - sharing.save() - sharing.anvil_create_or_update() - elif isinstance(self.audit_result, upload_workspace_audit.ShareAsOwner): - sharing.access = WorkspaceGroupSharing.OWNER - sharing.can_compute = True - sharing.full_clean() - sharing.save() - sharing.anvil_create_or_update() + if isinstance(self.audit_result, upload_workspace_audit.VerifiedShared): + # No changes needed. + pass + elif isinstance(self.audit_result, upload_workspace_audit.VerifiedNotShared): + # No changes needed. + pass elif isinstance(self.audit_result, upload_workspace_audit.StopSharing): sharing.anvil_delete() sharing.delete() + else: + if isinstance(self.audit_result, upload_workspace_audit.ShareAsReader): + sharing.access = WorkspaceGroupSharing.READER + sharing.can_compute = False + elif isinstance(self.audit_result, upload_workspace_audit.ShareAsWriter): + sharing.access = WorkspaceGroupSharing.WRITER + sharing.can_compute = False + elif isinstance(self.audit_result, upload_workspace_audit.ShareWithCompute): + sharing.access = WorkspaceGroupSharing.WRITER + sharing.can_compute = True + elif isinstance(self.audit_result, upload_workspace_audit.ShareAsOwner): + sharing.access = WorkspaceGroupSharing.OWNER + sharing.can_compute = True + sharing.full_clean() + sharing.save() + sharing.anvil_create_or_update() except AnVILAPIError as e: if self.request.htmx: return HttpResponse(self.htmx_error) From 502a02ba63a8bd8877d77120d12fadff5fc23482 Mon Sep 17 00:00:00 2001 From: amstilp <3944584+amstilp@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:29:37 +0000 Subject: [PATCH 043/125] Compile requirements files --- requirements/test-requirements.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/requirements/test-requirements.txt b/requirements/test-requirements.txt index 25be142a..cc792a59 100644 --- a/requirements/test-requirements.txt +++ b/requirements/test-requirements.txt @@ -29,6 +29,8 @@ factory-boy==3.3.0 # via -r requirements/test-requirements.in faker==23.2.1 # via factory-boy +freezegun==1.5.1 + # via -r requirements/test-requirements.in idna==3.7 # via # -c requirements/requirements.txt @@ -63,7 +65,9 @@ pytest-sugar==1.0.0 pytest-xdist==3.6.1 # via -r requirements/test-requirements.in python-dateutil==2.8.2 - # via faker + # via + # faker + # freezegun pyyaml==6.0.1 # via responses requests==2.32.3 From d3ee79f24b51d96d55451463a3a6b56a8514e15e Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 21 Aug 2024 15:24:59 -0700 Subject: [PATCH 044/125] Add tests for UploadWorkspaceAuditByWorkspace view --- .../gregor_anvil/tests/test_views.py | 398 +++++++++++++++++- .../upload_workspace_audit_action_button.html | 2 +- 2 files changed, 390 insertions(+), 10 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index a5770b52..2f81308a 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -2151,8 +2151,368 @@ def test_cc_admins_membership(self): class UploadWorkspaceAuditByWorkspaceTest(AnVILAPIMockTestMixin, TestCase): - def test_tests(self): - self.fail("write tests") + """Tests for the UploadWorkspaceAuditByWorkspace view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:upload_workspace", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuditByWorkspace.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url("foo", "bar")) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar"), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()( + request, + billing_project_slug=self.upload_workspace.workspace.billing_project.name, + workspace_slug=self.upload_workspace.workspace.name, + ) + + def test_invalid_billing_project_name(self): + """Raises a 404 error with an invalid object billing project.""" + request = self.factory.get(self.get_url("foo", self.upload_workspace.workspace.name)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()( + request, + billing_project_slug="foo", + workspace_slug=self.upload_workspace.workspace.name, + ) + + def test_invalid_workspace_name(self): + """Raises a 404 error with an invalid workspace name.""" + request = self.factory.get(self.get_url(self.upload_workspace.workspace.billing_project.name, "foo")) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()( + request, + billing_project_slug=self.upload_workspace.workspace.billing_project.name, + workspace_slug="foo", + ) + + def test_context_audit_results(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_audit.UploadWorkspaceAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(self.upload_workspace, audit_results.queryset) + + def test_context_audit_results_does_not_include_other_workspaces(self): + """The audit_results does not include other workspaces.""" + other_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + audit_results = response.context_data["audit_results"] + self.assertEqual(audit_results.queryset.count(), 1) + self.assertNotIn(other_workspace, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, group=group, access=acm_models.WorkspaceGroupSharing.OWNER + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), acm_models.WorkspaceGroupSharing.OWNER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_reader(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = self.upload_workspace.workspace.authorization_domains.first() + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_writer(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create() + rc = self.upload_workspace.research_center + rc.uploader_group = group + rc.save() + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_with_compute(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_owner(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_stop_sharing(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + # Change upload workspace end dates so it's in the past. + self.upload_workspace.upload_cycle.start_date = timezone.now() - timedelta(days=20) + self.upload_workspace.upload_cycle.end_date = timezone.now() - timedelta(days=10) + self.upload_workspace.upload_cycle.save() + self.upload_workspace.date_qc_completed = timezone.now() - timedelta(days=1) + self.upload_workspace.save() + group = acm_factories.ManagedGroupFactory.create() + rc = self.upload_workspace.research_center + rc.uploader_group = group + rc.save() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_stop_sharing(self): + """error_table shows a record when an audit error is detected.""" + # Change upload workspace end dates so it's in the past. + group = acm_factories.ManagedGroupFactory.create() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=self.upload_workspace.workspace, + group=self.upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") class UploadWorkspaceAuditResolveTest(AnVILAPIMockTestMixin, TestCase): @@ -2228,10 +2588,20 @@ def test_access_without_user_permission(self): with self.assertRaises(PermissionDenied): self.get_view()(request) - def test_get_upload_workspace_does_not_exist(self): - """Raises a 404 error with an invalid object dbgap_application_pk.""" + def test_get_billing_project_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() self.client.force_login(self.user) - response = self.client.get(self.get_url("foo", "bar", "foobar")) + response = self.client.get(self.get_url("foo", upload_workspace.workspace.name, group.name)) + self.assertEqual(response.status_code, 404) + + def test_get_workspace_name_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.workspace.billing_project.name, "foo", group.name)) self.assertEqual(response.status_code, 404) def test_get_group_does_not_exist(self): @@ -2248,7 +2618,7 @@ def test_get_group_does_not_exist(self): self.assertEqual(response.status_code, 404) def test_get_context_audit_result(self): - """The data_access_audit exists in the context.""" + """The audit_results exists in the context.""" upload_workspace = factories.UploadWorkspaceFactory.create() group = acm_factories.ManagedGroupFactory.create() self.client.force_login(self.user) @@ -2356,10 +2726,20 @@ def test_get_share_as_owner(self): self.assertEqual(audit_result.managed_group, group) self.assertEqual(audit_result.note, upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER) - def test_post_upload_workspace_does_not_exist(self): - """Raises a 404 error with an invalid object dbgap_application_pk.""" + def test_post_billing_project_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post(self.get_url("foo", upload_workspace.workspace.name, group.name)) + self.assertEqual(response.status_code, 404) + + def test_post_workspace_name_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() self.client.force_login(self.user) - response = self.client.post(self.get_url("foo", "bar", "foobar")) + response = self.client.post(self.get_url(upload_workspace.workspace.billing_project.name, "foo", group.name)) self.assertEqual(response.status_code, 404) def test_post_group_does_not_exist(self): diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html index cb62c5ab..5a3fba93 100644 --- a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html @@ -1,6 +1,6 @@
    {% if record.action %} - {{ record.action }} FOO + {{ record.action }} {% else %} — {% endif %} From 2e4af69e18345aad89960b624cc7778b06daa0e2 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 09:56:19 -0700 Subject: [PATCH 045/125] Rename url in prep for adding auditing by upload cycle --- gregor_django/gregor_anvil/tests/test_views.py | 4 ++-- gregor_django/gregor_anvil/urls.py | 2 +- .../templates/gregor_anvil/uploadworkspace_detail.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 2f81308a..e9b6eea3 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1117,7 +1117,7 @@ def test_contains_audit_consortium_access_button(self): self.client.force_login(self.user) response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) url = reverse( - "gregor_anvil:audit:upload_workspaces:upload_workspace", + "gregor_anvil:audit:upload_workspaces:by_upload_workspace", args=[ self.object.workspace.billing_project.name, self.object.workspace.name, @@ -2167,7 +2167,7 @@ def setUp(self): def get_url(self, *args): """Get the url for the view being tested.""" return reverse( - "gregor_anvil:audit:upload_workspaces:upload_workspace", + "gregor_anvil:audit:upload_workspaces:by_upload_workspace", args=args, ) diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 6824756f..510e61e5 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -56,7 +56,7 @@ path( "//", views.UploadWorkspaceAuditByWorkspace.as_view(), - name="upload_workspace", + name="by_upload_workspace", ), # path("upload_cycle//", views.UploadWorkspaceAuditByUploadCycle.as_view(), name="upload_cycle"), ], diff --git a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html index 7f8cb82b..f1ae35ff 100644 --- a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html @@ -41,7 +41,7 @@

    {% block action_buttons %} {% if show_edit_links %}

    - + Audit consortium sharing From a0e72a66dddb6f05ac19ee1faca9dfb83295b35e Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 10:15:59 -0700 Subject: [PATCH 046/125] Add view to audit all upload workspaces for a given cycle Note that this is likely the view that we will use the most. --- .../gregor_anvil/tests/test_views.py | 334 ++++++++++++++++++ gregor_django/gregor_anvil/urls.py | 2 +- gregor_django/gregor_anvil/views.py | 34 ++ 3 files changed, 369 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index e9b6eea3..106477ef 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -2515,6 +2515,340 @@ def test_context_error_table_stop_sharing(self): self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") +class UploadWorkspaceAuditByUploadCycleTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceAuditByUploadCycle view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.upload_cycle = factories.UploadCycleFactory.create(is_future=True) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:by_upload_cycle", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuditByUploadCycle.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url(self.upload_cycle.cycle + 1)) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(self.upload_cycle.cycle + 1), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url(self.upload_cycle.cycle)) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request, cycle=self.upload_cycle.cycle) + + def test_invalid_upload_cycle(self): + """Raises a 404 error with an invalid upload cycle.""" + request = self.factory.get(self.get_url(self.upload_cycle.cycle + 1)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()(request, cycle=self.upload_cycle.cycle + 1) + + def test_context_audit_results_no_upload_workspaces(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_audit.UploadWorkspaceAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + + def test_context_audit_results_one_upload_workspace(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_audit.UploadWorkspaceAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(upload_workspace, audit_results.queryset) + + def test_context_audit_results_two_upload_workspaces(self): + """The audit_results exists in the context.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + upload_workspace_2 = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_audit.UploadWorkspaceAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 2) + self.assertIn(upload_workspace_1, audit_results.queryset) + self.assertIn(upload_workspace_2, audit_results.queryset) + + def test_context_audit_results_ignores_other_upload_cycles(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__cycle=self.upload_cycle.cycle + 1) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_audit.UploadWorkspaceAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + self.assertNotIn(upload_workspace, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=acm_models.WorkspaceGroupSharing.OWNER + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), acm_models.WorkspaceGroupSharing.OWNER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_reader(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = upload_workspace.workspace.authorization_domains.first() + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.AUTH_DOMAIN_AS_READER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_writer(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=self.upload_cycle, + research_center__uploader_group=group, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_with_compute(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.DCC_WRITERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_owner(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_stop_sharing(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + # Change upload workspace end dates so it's in the past. + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle=self.upload_cycle, + research_center__uploader_group=group, + date_qc_completed=timezone.now() - timedelta(days=1), + ) + self.upload_cycle.start_date = timezone.now() - timedelta(days=20) + self.upload_cycle.end_date = timezone.now() - timedelta(days=10) + self.upload_cycle.save() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_stop_sharing(self): + """error_table shows a record when an audit error is detected.""" + # Change upload workspace end dates so it's in the past. + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_audit.UploadWorkspaceAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + class UploadWorkspaceAuditResolveTest(AnVILAPIMockTestMixin, TestCase): def setUp(self): """Set up test class.""" diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 510e61e5..eaad0a61 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -53,12 +53,12 @@ views.UploadWorkspaceAuditResolve.as_view(), name="resolve", ), + path("upload_cycle//", views.UploadWorkspaceAuditByUploadCycle.as_view(), name="by_upload_cycle"), path( "//", views.UploadWorkspaceAuditByWorkspace.as_view(), name="by_upload_workspace", ), - # path("upload_cycle//", views.UploadWorkspaceAuditByUploadCycle.as_view(), name="upload_cycle"), ], "upload_workspaces", ) diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index f13dc6df..37f2ba70 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -208,6 +208,40 @@ def get_context_data(self, **kwargs): return context +class UploadWorkspaceAuditByUploadCycle(AnVILConsortiumManagerStaffViewRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_audit.html" + model = models.UploadCycle + + def get_object(self, queryset=None): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + cycle = self.kwargs.get("cycle", None) + queryset = self.model.objects.filter(cycle=cycle) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_audit.UploadWorkspaceAudit( + queryset=models.UploadWorkspace.objects.filter(upload_cycle=self.object) + ) + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + class UploadWorkspaceAuditResolve(AnVILConsortiumManagerStaffEditRequired, FormView): """View to resolve UploadWorkspace audit results.""" From 6904f42ce0afc8b9bb15ee67e484891ebfc20969 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 10:23:13 -0700 Subject: [PATCH 047/125] Add non-member RC group to test data script --- add_upload_workspace_audit_test_data.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/add_upload_workspace_audit_test_data.py b/add_upload_workspace_audit_test_data.py index 51ce1df1..429e693e 100644 --- a/add_upload_workspace_audit_test_data.py +++ b/add_upload_workspace_audit_test_data.py @@ -10,7 +10,8 @@ dcc_admin_group = ManagedGroupFactory(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) dcc_writer_group = ManagedGroupFactory(name="GREGOR_DCC_WRITERS") rc_1_member_group = ManagedGroupFactory(name="DEMO_RC1_MEMBERS") -rc_1_uploader_group = ManagedGroupFactory(name="DMEO_RC1_UPLOADERS") +rc_1_uploader_group = ManagedGroupFactory(name="DEMO_RC1_UPLOADERS") +rc_1_nonmember_group = ManagedGroupFactory(name="DEMO_RC1_NONMEMBERS") # Create an RC rc = factories.ResearchCenterFactory.create( @@ -18,6 +19,7 @@ short_name="RC1", member_group=rc_1_member_group, uploader_group=rc_1_uploader_group, + non_member_group=rc_1_nonmember_group, ) @@ -39,12 +41,13 @@ is_current=True, is_ready_for_compute=False, ) -factories.UploadWorkspaceFactory.create( +workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, research_center=rc, workspace__name="TEST_U02_RC1", ) + # Create a current upload cycle before compute. upload_cycle = factories.UploadCycleFactory.create( cycle=3, From 16c0119239fb5d97a441da308424bc18402c9f11 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 10:36:33 -0700 Subject: [PATCH 048/125] Add distinct keyword to group selection for workspace auditing --- gregor_django/gregor_anvil/audit/upload_workspace_audit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index e196d0c8..6d0af9d7 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -236,7 +236,7 @@ def audit_upload_workspace(self, upload_workspace): | # Groups that the workspace is shared with. Q(workspacegroupsharing__workspace=upload_workspace.workspace) - ) + ).distinct() for group in groups_to_audit: self.audit_workspace_and_group(upload_workspace, group) From 19ab046f97aff603e82f57c753d76aff9630cf87 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 10:40:56 -0700 Subject: [PATCH 049/125] Switch action button for upload workspaces to use htmx --- .../upload_workspace_audit_action_button.html | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html index 5a3fba93..06d55203 100644 --- a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html @@ -1,6 +1,21 @@

    {% if record.action %} - {{ record.action }} +
    + + {% csrf_token %} + +
    + {% else %} — {% endif %} From 1ff62269f06cca3a6f44d8f896f6cc70117fd6f0 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 10:45:04 -0700 Subject: [PATCH 050/125] Add sharing records to the test data For each point in the upload cycle, add sharing records based on what they would have been at the previous point in the upload cycle. This allows us to see what will change at each step during the audit. --- add_upload_workspace_audit_test_data.py | 144 ++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 9 deletions(-) diff --git a/add_upload_workspace_audit_test_data.py b/add_upload_workspace_audit_test_data.py index 429e693e..e33783cb 100644 --- a/add_upload_workspace_audit_test_data.py +++ b/add_upload_workspace_audit_test_data.py @@ -1,5 +1,7 @@ +from anvil_consortium_manager.models import WorkspaceGroupSharing from anvil_consortium_manager.tests.factories import ( ManagedGroupFactory, + WorkspaceGroupSharingFactory, ) from django.conf import settings from django.utils import timezone @@ -29,7 +31,7 @@ is_future=True, is_ready_for_compute=False, ) -factories.UploadWorkspaceFactory.create( +workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, research_center=rc, workspace__name="TEST_U01_RC1", @@ -46,45 +48,154 @@ research_center=rc, workspace__name="TEST_U02_RC1", ) +# Create records as appropriate for the previous point in the cycle - future cycle. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# DCC writers. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) +# RC uploaders. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=rc_1_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=False, +) -# Create a current upload cycle before compute. +# Create a current upload cycle after compute. upload_cycle = factories.UploadCycleFactory.create( cycle=3, is_current=True, is_ready_for_compute=True, ) -factories.UploadWorkspaceFactory.create( +workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, research_center=rc, workspace__name="TEST_U03_RC1", ) +# Create records as appropriate for the previous point in the cycle - current cycle before compute. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# DCC writers. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) +# RC uploaders. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=rc_1_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=False, +) -# Create a past upload cycle before QC is completed. +# Create a past upload cycle before qc is completed. upload_cycle = factories.UploadCycleFactory.create( cycle=4, - is_current=True, + is_past=True, is_ready_for_compute=True, ) -factories.UploadWorkspaceFactory.create( +workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, research_center=rc, workspace__name="TEST_U04_RC1", date_qc_completed=None, ) +# Create records as appropriate for the previous point in the cycle - current cycle after compute. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# DCC writers. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) +# RC uploaders. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=rc_1_uploader_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) # Create a past upload cycle after QC is completed. upload_cycle = factories.UploadCycleFactory.create( cycle=5, - is_current=True, + is_past=True, is_ready_for_compute=True, ) -factories.UploadWorkspaceFactory.create( +workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, research_center=rc, workspace__name="TEST_U05_RC1", date_qc_completed=timezone.now(), ) +# Create records as appropriate for the previous point in the cycle - past cycle before QC complete. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) +# DCC writers. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_writer_group, + access=WorkspaceGroupSharing.WRITER, + can_compute=True, +) # Create a past upload cycle with a combined workspace. upload_cycle = factories.UploadCycleFactory.create( @@ -92,7 +203,7 @@ is_past=True, is_ready_for_compute=True, ) -factories.UploadWorkspaceFactory.create( +workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, research_center=rc, workspace__name="TEST_U06_RC1", @@ -103,3 +214,18 @@ date_completed=timezone.now(), workspace__name="TEST_U06_COMBINED", ) +# Create records as appropriate for the previous point in the cycle - past cycle before QC complete. +# Auth domain. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + can_compute=False, +) +# DCC admins. +WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=dcc_admin_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, +) From 8df9dc58ba3181098c37d688c138436005d3a158 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 11:11:48 -0700 Subject: [PATCH 051/125] Add a BooleanIconColumn to show icons for True/False --- gregor_django/gregor_anvil/tables.py | 46 +++++++++++++++ .../gregor_anvil/tests/test_tables.py | 57 +++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/gregor_django/gregor_anvil/tables.py b/gregor_django/gregor_anvil/tables.py index 236c98c2..1ba4f4ac 100644 --- a/gregor_django/gregor_anvil/tables.py +++ b/gregor_django/gregor_anvil/tables.py @@ -6,6 +6,52 @@ from . import models +class BooleanIconColumn(tables.BooleanColumn): + """A column that renders a boolean value as an icon. + + This column renders a boolean value as a bootstrap icon. By default, the icon is a green checkmark for True, + and nothing for False, but this can be customized by keyword arguments. + + Args: + show_false_icon (bool): Whether to show an icon for False values. + true_color (str): The color of the icon for True values. (Default: green) + false_color (str): The color of the icon for False values. (Default: red) + true_icon (str): The icon to use for True values. (Default: check-circle-fill) + false_icon (str): The icon to use for False values. (Default: x-circle-fill) + """ + + def __init__( + self, + show_false_icon=False, + true_color="green", + false_color="red", + true_icon="check-circle-fill", + false_icon="x-circle-fill", + **kwargs, + ): + super().__init__(**kwargs) + self.show_false_icon = show_false_icon + self.true_color = true_color + self.false_color = false_color + self.true_icon = true_icon + self.false_icon = false_icon + + def render(self, value, record, bound_column): + value = self._get_bool_value(record, value, bound_column) + if value: + rendered_value = format_html( + f"""""" + ) + else: + if self.show_false_icon: + rendered_value = format_html( + f"""""" # noqa: E501 + ) + else: + rendered_value = "" + return rendered_value + + class AccountTable(tables.Table): """A custom table for `Accounts`.""" diff --git a/gregor_django/gregor_anvil/tests/test_tables.py b/gregor_django/gregor_anvil/tests/test_tables.py index bafc2386..fb5d90c4 100644 --- a/gregor_django/gregor_anvil/tests/test_tables.py +++ b/gregor_django/gregor_anvil/tests/test_tables.py @@ -15,6 +15,63 @@ from . import factories +class BooleanIconColumnTest(TestCase): + """Tests for the BooleanIconColumn class.""" + + def test_render_default(self): + """render method with defaults.""" + column = tables.BooleanIconColumn() + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("green", value) + value = column.render(False, None, None) + self.assertEqual(value, "") + + def test_render_show_false_icon(self): + """render method with defaults.""" + column = tables.BooleanIconColumn(show_false_icon=True) + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("green", value) + value = column.render(False, None, None) + self.assertIn("bi-x-circle-fill", value) + self.assertIn("red", value) + + def test_true_color(self): + column = tables.BooleanIconColumn(true_color="blue") + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("blue", value) + value = column.render(False, None, None) + self.assertEqual(value, "") + + def test_true_icon(self): + column = tables.BooleanIconColumn(true_icon="dash") + value = column.render(True, None, None) + self.assertIn("bi-dash", value) + self.assertIn("green", value) + value = column.render(False, None, None) + self.assertEqual(value, "") + + def test_false_color(self): + column = tables.BooleanIconColumn(show_false_icon=True, false_color="blue") + value = column.render(False, None, None) + self.assertIn("bi-x-circle-fill", value) + self.assertIn("blue", value) + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("green", value) + + def test_false_icon(self): + column = tables.BooleanIconColumn(show_false_icon=True, false_icon="dash") + value = column.render(False, None, None) + self.assertIn("bi-dash", value) + self.assertIn("red", value) + value = column.render(True, None, None) + self.assertIn("bi-check-circle-fill", value) + self.assertIn("green", value) + + class AccountTableTest(TestCase): """Tests for the AccountTable in this app.""" From e8006f950cd65cd3eaaf44f0a9130f97992f4dbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 19:22:11 +0000 Subject: [PATCH 052/125] Bump werkzeug from 3.0.3 to 3.0.4 Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.3 to 3.0.4. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.0.3...3.0.4) --- updated-dependencies: - dependency-name: werkzeug dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 16e31a22..5b6bf856 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -233,7 +233,7 @@ wcwidth==0.2.13 # via prompt-toolkit websockets==12.0 # via sphinx-autobuild -werkzeug==3.0.3 +werkzeug==3.0.4 # via -r requirements/dev-requirements.in # The following packages are considered to be unsafe in a requirements file: From 3fa3d217a009b601b113e49f8157acdb5183bd83 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 12:35:57 -0700 Subject: [PATCH 053/125] Use the new BooleanIconColumn in the audit tables --- gregor_django/gregor_anvil/audit/upload_workspace_audit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 6d0af9d7..1bb0fb90 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -6,6 +6,7 @@ from django.db.models import Q, QuerySet from ..models import CombinedConsortiumDataWorkspace, UploadWorkspace +from ..tables import BooleanIconColumn # from primed.primed_anvil.tables import BooleanIconColumn from .base import GREGoRAudit, GREGoRAuditResult @@ -136,7 +137,7 @@ class UploadWorkspaceAuditTable(tables.Table): managed_group = tables.Column(linkify=True) # is_shared = tables.Column() access = tables.Column() - can_compute = tables.Column() + can_compute = BooleanIconColumn(show_false_icon=True, null=True) note = tables.Column() # action = tables.Column() action = tables.TemplateColumn(template_name="gregor_anvil/snippets/upload_workspace_audit_action_button.html") From 25500cb4805854c9508f08f8996e2724c8d29249 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 14:27:37 -0700 Subject: [PATCH 054/125] Change is_ready_for_compute to a date field This gives us a little more information while accomplishing the same thing. --- add_upload_workspace_audit_test_data.py | 8 ++--- .../audit/upload_workspace_audit.py | 4 +-- ...dy_to_compute_to_date_ready_for_compute.py | 31 +++++++++++++++++++ gregor_django/gregor_anvil/models.py | 7 +++-- gregor_django/gregor_anvil/tests/factories.py | 1 + .../gregor_anvil/tests/test_audit.py | 8 +++-- .../gregor_anvil/tests/test_models.py | 12 +++---- .../gregor_anvil/uploadcycle_detail.html | 8 ++--- 8 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 gregor_django/gregor_anvil/migrations/0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute.py diff --git a/add_upload_workspace_audit_test_data.py b/add_upload_workspace_audit_test_data.py index e33783cb..01ed9ceb 100644 --- a/add_upload_workspace_audit_test_data.py +++ b/add_upload_workspace_audit_test_data.py @@ -29,7 +29,6 @@ upload_cycle = factories.UploadCycleFactory.create( cycle=1, is_future=True, - is_ready_for_compute=False, ) workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, @@ -41,7 +40,6 @@ upload_cycle = factories.UploadCycleFactory.create( cycle=2, is_current=True, - is_ready_for_compute=False, ) workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, @@ -83,8 +81,9 @@ upload_cycle = factories.UploadCycleFactory.create( cycle=3, is_current=True, - is_ready_for_compute=True, ) +upload_cycle.date_ready_for_compute = upload_cycle.start_date +upload_cycle.save() workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, research_center=rc, @@ -124,7 +123,6 @@ upload_cycle = factories.UploadCycleFactory.create( cycle=4, is_past=True, - is_ready_for_compute=True, ) workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, @@ -166,7 +164,6 @@ upload_cycle = factories.UploadCycleFactory.create( cycle=5, is_past=True, - is_ready_for_compute=True, ) workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, @@ -201,7 +198,6 @@ upload_cycle = factories.UploadCycleFactory.create( cycle=6, is_past=True, - is_ready_for_compute=True, ) workspace = factories.UploadWorkspaceFactory.create( upload_cycle=upload_cycle, diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 1bb0fb90..285a4ee2 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -304,7 +304,7 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group **audit_result_args, ) ) - elif upload_cycle.is_current and not upload_cycle.is_ready_for_compute: + elif upload_cycle.is_current and not upload_cycle.date_ready_for_compute: note = self.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE if ( current_sharing @@ -324,7 +324,7 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group **audit_result_args, ) ) - elif upload_cycle.is_current and upload_cycle.is_ready_for_compute: + elif upload_cycle.is_current and upload_cycle.date_ready_for_compute: note = self.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE if ( current_sharing diff --git a/gregor_django/gregor_anvil/migrations/0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute.py new file mode 100644 index 00000000..309c19e5 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.15 on 2024-08-22 21:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gregor_anvil', '0030_researchcenter_non_member_group'), + ] + + operations = [ + migrations.RemoveField( + model_name='historicaluploadcycle', + name='is_ready_for_compute', + ), + migrations.RemoveField( + model_name='uploadcycle', + name='is_ready_for_compute', + ), + migrations.AddField( + model_name='historicaluploadcycle', + name='date_ready_for_compute', + field=models.DateField(blank=True, default=None, help_text='Date that this workspace was ready for RC uploaders to run compute.', null=True), + ), + migrations.AddField( + model_name='uploadcycle', + name='date_ready_for_compute', + field=models.DateField(blank=True, default=None, help_text='Date that this workspace was ready for RC uploaders to run compute.', null=True), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 64a26764..b721ad54 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -159,8 +159,11 @@ class UploadCycle(TimeStampedModel, models.Model): ) start_date = models.DateField(help_text="The start date of this upload cycle.") end_date = models.DateField(help_text="The end date of this upload cycle.") - is_ready_for_compute = models.BooleanField( - help_text="Boolean indicator of whether workspace writers should be able to run compute.", default=False + date_ready_for_compute = models.DateField( + help_text="Date that this workspace was ready for RC uploaders to run compute.", + blank=True, + null=True, + default=None, ) note = models.TextField(blank=True, help_text="Additional notes.") diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 50d8a6af..2a1f64a0 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -43,6 +43,7 @@ class Params: is_past = Trait( start_date=timezone.localdate() - timedelta(days=100), end_date=timezone.localdate() - timedelta(days=10), + date_ready_for_compute=timezone.localdate() - timedelta(days=90), ) is_current = Trait( start_date=timezone.localdate() - timedelta(days=45), diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index ccf4cefc..92741654 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -885,7 +885,7 @@ def setUp(self): self.upload_workspace = factories.UploadWorkspaceFactory.create( research_center=self.research_center, upload_cycle__is_current=True, - upload_cycle__is_ready_for_compute=False, + upload_cycle__date_ready_for_compute=None, ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() self.other_group = ManagedGroupFactory.create() @@ -1443,8 +1443,12 @@ def setUp(self): self.rc_uploader_group = ManagedGroupFactory.create() self.research_center = factories.ResearchCenterFactory.create(uploader_group=self.rc_uploader_group) self.upload_workspace = factories.UploadWorkspaceFactory.create( - research_center=self.research_center, upload_cycle__is_current=True, upload_cycle__is_ready_for_compute=True + research_center=self.research_center, + upload_cycle__is_current=True, ) + # Set date ready for compute to a non-null value. + self.upload_workspace.upload_cycle.date_ready_for_compute = self.upload_workspace.upload_cycle.start_date + self.upload_workspace.upload_cycle.save() self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() self.other_group = ManagedGroupFactory.create() self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index f9883337..2273b3c0 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -219,7 +219,7 @@ def test_model_saving(self): instance.full_clean() instance.save() self.assertIsInstance(instance, models.UploadCycle) - self.assertEqual(instance.is_ready_for_compute, False) + self.assertIsNone(instance.date_ready_for_compute) def test_model_saving_with_note(self): """Creation using the model constructor and .save() works.""" @@ -410,7 +410,6 @@ def test_get_partner_upload_workspaces_full_test(self): version=2, date_completed=None, ) - # import ipdb; ipdb.set_trace() included_workspaces = upload_cycle.get_partner_upload_workspaces() self.assertEqual(included_workspaces.count(), 2) self.assertNotIn(workspace_1, included_workspaces) @@ -418,13 +417,14 @@ def test_get_partner_upload_workspaces_full_test(self): self.assertIn(workspace_3, included_workspaces) self.assertNotIn(workspace_4, included_workspaces) - def test_is_ready_for_compute(self): + def test_date_ready_for_compute(self): """UploadCycle is ready for compute if all PartnerUploadWorkspaces have date_completed.""" upload_cycle = factories.UploadCycleFactory.create() - self.assertFalse(upload_cycle.is_ready_for_compute) - upload_cycle.is_ready_for_compute = True + self.assertIsNone(upload_cycle.date_ready_for_compute) + date = timezone.localdate() + upload_cycle.date_ready_for_compute = date upload_cycle.save() - self.assertEqual(upload_cycle.is_ready_for_compute, True) + self.assertEqual(upload_cycle.date_ready_for_compute, date) def test_is_current_is_past_is_future(self): # Previous cycle. diff --git a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html index a93dea16..e86c51eb 100644 --- a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html @@ -17,11 +17,11 @@
  • Start date: {{ object.start_date }}
  • End date: {{ object.end_date }}
  • - Ready for compute: - {% if object.is_ready_for_compute %} - Yes + Date ready for compute: + {% if object.date_ready_for_compute %} + {{ object.date_ready_for_compute }} {% else %} - No + — {% endif %}
  • From a4c33f1a7458d14b395896d19f76daeb9c5aef34 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 14:43:13 -0700 Subject: [PATCH 055/125] Change date_qc_completed to a DateField We don't need the timestamp and date fields are easier to fill in. --- ...oadworkspace_date_qc_completed_and_more.py | 23 +++++++++++++++++++ gregor_django/gregor_anvil/models.py | 2 +- .../gregor_anvil/tests/test_audit.py | 8 ++----- .../gregor_anvil/tests/test_views.py | 4 ++-- 4 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 gregor_django/gregor_anvil/migrations/0032_alter_historicaluploadworkspace_date_qc_completed_and_more.py diff --git a/gregor_django/gregor_anvil/migrations/0032_alter_historicaluploadworkspace_date_qc_completed_and_more.py b/gregor_django/gregor_anvil/migrations/0032_alter_historicaluploadworkspace_date_qc_completed_and_more.py new file mode 100644 index 00000000..17ef5e04 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0032_alter_historicaluploadworkspace_date_qc_completed_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2024-08-22 21:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gregor_anvil', '0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute'), + ] + + operations = [ + migrations.AlterField( + model_name='historicaluploadworkspace', + name='date_qc_completed', + field=models.DateField(blank=True, default=None, help_text='Date that QC was completed for this workspace. If null, QC is not complete.', null=True), + ), + migrations.AlterField( + model_name='uploadworkspace', + name='date_qc_completed', + field=models.DateField(blank=True, default=None, help_text='Date that QC was completed for this workspace. If null, QC is not complete.', null=True), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index b721ad54..791555ec 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -233,7 +233,7 @@ class UploadWorkspace(TimeStampedModel, BaseWorkspaceData): upload_cycle = models.ForeignKey(UploadCycle, on_delete=models.PROTECT) """The UploadCycle associated with this workspace.""" - date_qc_completed = models.DateTimeField( + date_qc_completed = models.DateField( help_text="Date that QC was completed for this workspace. If null, QC is not complete.", blank=True, null=True, diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 92741654..e0de8ba9 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -2576,9 +2576,7 @@ def setUp(self): self.upload_workspace = factories.UploadWorkspaceFactory.create( research_center=self.research_center, upload_cycle__is_past=True, - date_qc_completed=fake.date_time_this_year( - before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() - ), + date_qc_completed=fake.date_this_year(before_today=True, after_today=False), ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() self.other_group = ManagedGroupFactory.create() @@ -3148,9 +3146,7 @@ def setUp(self): self.upload_workspace = factories.UploadWorkspaceFactory.create( research_center=self.research_center, upload_cycle__is_past=True, - date_qc_completed=fake.date_time_this_year( - before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() - ), + date_qc_completed=fake.date_this_year(before_today=True, after_today=False), ) self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() # Create a corresponding combined workspace. diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 106477ef..f7b2102b 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -2432,7 +2432,7 @@ def test_context_needs_action_table_stop_sharing(self): self.upload_workspace.upload_cycle.start_date = timezone.now() - timedelta(days=20) self.upload_workspace.upload_cycle.end_date = timezone.now() - timedelta(days=10) self.upload_workspace.upload_cycle.save() - self.upload_workspace.date_qc_completed = timezone.now() - timedelta(days=1) + self.upload_workspace.date_qc_completed = timezone.localdate() - timedelta(days=1) self.upload_workspace.save() group = acm_factories.ManagedGroupFactory.create() rc = self.upload_workspace.research_center @@ -2776,7 +2776,7 @@ def test_context_needs_action_table_stop_sharing(self): upload_workspace = factories.UploadWorkspaceFactory.create( upload_cycle=self.upload_cycle, research_center__uploader_group=group, - date_qc_completed=timezone.now() - timedelta(days=1), + date_qc_completed=timezone.localdate() - timedelta(days=1), ) self.upload_cycle.start_date = timezone.now() - timedelta(days=20) self.upload_cycle.end_date = timezone.now() - timedelta(days=10) From b2f458dbc57b58d57a6226312fdaa259f1a3548b Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 14:47:28 -0700 Subject: [PATCH 056/125] Change date_completed to a DateField for the combined workspace The timestamp is not necessary and it's easier to fill in a date field. --- ...umdataworkspace_date_completed_and_more.py | 23 +++++++++++++++++++ gregor_django/gregor_anvil/models.py | 2 +- .../gregor_anvil/tests/test_audit.py | 6 ++--- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 gregor_django/gregor_anvil/migrations/0033_alter_combinedconsortiumdataworkspace_date_completed_and_more.py diff --git a/gregor_django/gregor_anvil/migrations/0033_alter_combinedconsortiumdataworkspace_date_completed_and_more.py b/gregor_django/gregor_anvil/migrations/0033_alter_combinedconsortiumdataworkspace_date_completed_and_more.py new file mode 100644 index 00000000..ffb73a3e --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0033_alter_combinedconsortiumdataworkspace_date_completed_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2024-08-22 21:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gregor_anvil', '0032_alter_historicaluploadworkspace_date_qc_completed_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='combinedconsortiumdataworkspace', + name='date_completed', + field=models.DateField(blank=True, default=None, help_text='Date that data preparation in this workspace was completed.', null=True), + ), + migrations.AlterField( + model_name='historicalcombinedconsortiumdataworkspace', + name='date_completed', + field=models.DateField(blank=True, default=None, help_text='Date that data preparation in this workspace was completed.', null=True), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 791555ec..8cd5ffec 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -298,7 +298,7 @@ class CombinedConsortiumDataWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track a workspace that has data combined from multiple upload workspaces.""" upload_cycle = models.ForeignKey(UploadCycle, on_delete=models.PROTECT) - date_completed = models.DateTimeField( + date_completed = models.DateField( help_text="Date that data preparation in this workspace was completed.", blank=True, null=True, diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index e0de8ba9..173db996 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -7,7 +7,6 @@ from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceGroupSharingFactory from django.conf import settings from django.test import TestCase, override_settings -from django.utils import timezone from faker import Faker from ..audit import upload_workspace_audit @@ -3152,8 +3151,9 @@ def setUp(self): # Create a corresponding combined workspace. self.combined_workspace = factories.CombinedConsortiumDataWorkspaceFactory.create( upload_cycle=self.upload_workspace.upload_cycle, - date_completed=fake.date_time_this_year( - before_now=True, after_now=False, tzinfo=timezone.get_current_timezone() + date_completed=fake.date_this_year( + before_today=True, + after_today=False, ), ) self.other_group = ManagedGroupFactory.create() From aac2753ad7e935f22db1c3a06169d1bc8b11ce41 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 15:07:04 -0700 Subject: [PATCH 057/125] Add second exception to try/except block The AnVILGroupNotFound exception is returned when you try to share a workspace with a group, but the group does not exist in AnVIL. The view that resolves the audit should handle this like an API error (even though the return code is success - 200 - it is not actually a success). Without this exception in the try/except block, the htmx functionality did not return either "Handled!" or "Error!", which is confusing to the user. --- .../gregor_anvil/tests/test_views.py | 39 +++++++++++++++++++ gregor_django/gregor_anvil/views.py | 3 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index f7b2102b..33d79c67 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -3930,3 +3930,42 @@ def test_post_new_stop_sharing_anvil_api_error_htmx(self): # No messages were added. messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(len(messages), 0) + + def test_post_new_share_as_writer_group_not_found_on_anvil_htmx(self): + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + upload_cycle__is_future=True, + research_center__uploader_group=group, + ) + acls = [ + { + "email": group.email, + "accessLevel": "WRITER", + "canShare": False, + "canCompute": False, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/test-bp/test-ws/acl?inviteUsersNotFound=false", + # Successful error code, but with usersNotFound + status=200, + json={"invitesSent": [], "usersNotFound": acls, "usersUpdated": []}, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuditResolve.htmx_error) + # The sharing object was not deleted. + self.assertEqual(acm_models.WorkspaceGroupSharing.objects.count(), 0) + # sharing.refresh_from_db() + # self.assertEqual(sharing.created, date_created) + # self.assertEqual(sharing.modified, date_created) + # No messages were added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 37f2ba70..6790f5bb 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -3,6 +3,7 @@ AnVILConsortiumManagerStaffEditRequired, AnVILConsortiumManagerStaffViewRequired, ) +from anvil_consortium_manager.exceptions import AnVILGroupNotFound from anvil_consortium_manager.models import Account, ManagedGroup, Workspace, WorkspaceGroupSharing from django.contrib import messages from django.contrib.auth import get_user_model @@ -343,7 +344,7 @@ def form_valid(self, form): sharing.full_clean() sharing.save() sharing.anvil_create_or_update() - except AnVILAPIError as e: + except (AnVILAPIError, AnVILGroupNotFound) as e: if self.request.htmx: return HttpResponse(self.htmx_error) else: From e7e4b28d9b3df10e8a15655b26a551c9847d1983 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 15:58:49 -0700 Subject: [PATCH 058/125] Add explanation template text for UploadWorkspace audits --- .../upload_workspace_audit_explanation.html | 48 ++++++++++++++++++- .../gregor_anvil/upload_workspace_audit.html | 12 +++-- .../upload_workspace_audit_resolve.html | 4 +- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_explanation.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_explanation.html index dd6d86a4..d53023d6 100644 --- a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_explanation.html +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_explanation.html @@ -1 +1,47 @@ -XXX +
    +
    +

    + +

    +
    +
    + +

    + This audit checks that workspace sharing is appropriate for the current point in the upload cycle. + Sharing with the following groups are checked: +

      +
    • The authorization domain of the workspace
    • +
    • GREGOR_DCC_ADMINS
    • +
    • GREGOR_DCC_WRITERS
    • +
    • The upload group for the Research Center associated with this upload workspace
    • +
    • Any additional groups that the workspace is shared with
    • +
    + Note that groups associated with AnVIL (e.g., anvil-admins, anvil-devs) are ignored by the audit. +

    +

    The audit result categories are explained below. +

      + +
    • Verified includes the following:
    • +
        +
      • The workspace is shared with a group with appropriate permissions.
      • +
      • The workspace is not shared with a group that it should not be shared with.
      • +
      + +
    • Needs action includes the following:
    • +
        +
      • XXX
      • +
      + +
    • Errors
    • +
        +
      • XXX
      • +
      +
    +

    +

    Any errors should be reported!

    +
    +
    +
    +
    diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_audit.html b/gregor_django/templates/gregor_anvil/upload_workspace_audit.html index a772ea40..4edcde6e 100644 --- a/gregor_django/templates/gregor_anvil/upload_workspace_audit.html +++ b/gregor_django/templates/gregor_anvil/upload_workspace_audit.html @@ -6,16 +6,20 @@ {% block content %} -

    Upload workspace audit

    +

    Upload workspace audit: {{ object }}

    - Auditing sharing for {% if object %}{{ object }}{% else %}all upload workspaces{% endif %}. +

    + Auditing workspace sharing for {{ object }}. + All records in the "Needs action" table should be handled by clicking on the action button. + Any records in the "Errors" table should be reported. +

    + {% include "gregor_anvil/snippets/upload_workspace_audit_explanation.html" %} +

    Audit results

    -{% include "gregor_anvil/snippets/upload_workspace_audit_explanation.html" %} - {% include "__audit_tables.html" with verified_table=verified_table needs_action_table=needs_action_table errors_table=errors_table %} {% endblock content %} diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_audit_resolve.html b/gregor_django/templates/gregor_anvil/upload_workspace_audit_resolve.html index 7ee9c159..7cdcd7d0 100644 --- a/gregor_django/templates/gregor_anvil/upload_workspace_audit_resolve.html +++ b/gregor_django/templates/gregor_anvil/upload_workspace_audit_resolve.html @@ -14,11 +14,13 @@

    Resolve upload workspace audit

  • Upload workspace: {{ audit_result.workspace }}
  • Managed group: {{ audit_result.managed_group }}
  • + + {% include "gregor_anvil/snippets/upload_workspace_audit_explanation.html" %} +

    Audit results

    -{% include "gregor_anvil/snippets/upload_workspace_audit_explanation.html" %}
    From 17862283d2c3e8ca6b593803f50b9a3a1281390c Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 16:10:33 -0700 Subject: [PATCH 059/125] Add a link to the audit page on upload cycle detail page --- gregor_django/gregor_anvil/tests/test_views.py | 7 +++++++ .../templates/gregor_anvil/uploadcycle_detail.html | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 33d79c67..5dd2bdfc 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -985,6 +985,13 @@ def test_partner_upload_workspace_table(self): self.assertIn(workspace.workspace, table.data) self.assertNotIn(other_workspace.workspace, table.data) + def test_link_to_audit(self): + """Response includes a link to the audit page.""" + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertContains(response, reverse("gregor_anvil:audit:upload_workspaces:by_upload_cycle", args=[obj.cycle])) + class UploadCycleListTest(TestCase): """Tests for the UploadCycleList view.""" diff --git a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html index e86c51eb..09ddb652 100644 --- a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html @@ -59,3 +59,14 @@

    Partner upload workspaces

    {% endblock after_panel %} + +{% block action_buttons %} +

    + + Audit consortium sharing + + + Audit auth domain membership + +

    +{% endblock action_buttons %} From 6cfb57070fd649e335f64ad7e70ec816f992ce1a Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 16:26:45 -0700 Subject: [PATCH 060/125] Subclass the UploadCycleForm forms for creating and updating We want different fields to be avilable when updating vs. creating an UploadCycle. Split the form into subclasses specific to each action. --- gregor_django/gregor_anvil/forms.py | 16 +++++ .../gregor_anvil/tests/test_forms.py | 67 ++++++++++++++++++- .../gregor_anvil/tests/test_views.py | 2 +- gregor_django/gregor_anvil/views.py | 2 +- 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/gregor_django/gregor_anvil/forms.py b/gregor_django/gregor_anvil/forms.py index 76990a07..cf864f6e 100644 --- a/gregor_django/gregor_anvil/forms.py +++ b/gregor_django/gregor_anvil/forms.py @@ -19,15 +19,31 @@ class Meta: "cycle", "start_date", "end_date", + "date_ready_for_compute", "note", ) widgets = { "start_date": CustomDateInput(), "end_date": CustomDateInput(), + "date_ready_for_compute": CustomDateInput(), } +class UploadCycleCreateForm(UploadCycleForm): + """Form to create an UploadCycle object.""" + + class Meta(UploadCycleForm.Meta): + exclude = ("date_ready_for_compute",) # noqa: DJ006 + + +class UploadCycleUpdateForm(UploadCycleForm): + """Form to update an UploadCycle object.""" + + class Meta(UploadCycleForm.Meta): + exclude = ("cycle",) # noqa: DJ006 + + class UploadWorkspaceForm(forms.ModelForm): """Form for a UploadWorkspace object.""" diff --git a/gregor_django/gregor_anvil/tests/test_forms.py b/gregor_django/gregor_anvil/tests/test_forms.py index 61ea420c..e29858f5 100644 --- a/gregor_django/gregor_anvil/tests/test_forms.py +++ b/gregor_django/gregor_anvil/tests/test_forms.py @@ -9,10 +9,10 @@ from . import factories -class UploadCycleForm(TestCase): - """Tests for the UploadCycleForm class.""" +class UploadCycleCreateFormTest(TestCase): + """Tests for the UploadCycleCreateForm class.""" - form_class = forms.UploadCycleForm + form_class = forms.UploadCycleCreateForm def test_valid(self): """Form is valid with necessary input.""" @@ -78,6 +78,67 @@ def test_valid_note(self): self.assertTrue(form.is_valid()) +class UploadCycleUpdateFormTest(TestCase): + """Tests for the UploadCycleUpdateForm class.""" + + form_class = forms.UploadCycleUpdateForm + + def test_valid(self): + """Form is valid with necessary input.""" + form_data = { + "start_date": date.today(), + "end_date": date.today() + timedelta(days=1), + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + + def test_invalid_missing_start_date(self): + """Form is invalid when missing start_date.""" + form_data = { + # "start_date": date.today(), + "end_date": date.today() + timedelta(days=1), + } + form = self.form_class(data=form_data) + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors), 1) + self.assertIn("start_date", form.errors) + self.assertEqual(len(form.errors["start_date"]), 1) + self.assertIn("required", form.errors["start_date"][0]) + + def test_invalid_missing_end_date(self): + """Form is invalid when missing cycle.""" + form_data = { + "start_date": date.today(), + # "end_date": date.today() + timedelta(days=1), + } + form = self.form_class(data=form_data) + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors), 1) + self.assertIn("end_date", form.errors) + self.assertEqual(len(form.errors["end_date"]), 1) + self.assertIn("required", form.errors["end_date"][0]) + + def test_valid_note(self): + """Form is valid with a note.""" + form_data = { + "start_date": date.today(), + "end_date": date.today() + timedelta(days=1), + "note": "my test note", + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + + def test_valid_date_ready_for_compute(self): + """Form is valid with date_ready_for_compute.""" + form_data = { + "start_date": date.today(), + "end_date": date.today() + timedelta(days=60), + "date_ready_for_compute": date.today() + timedelta(days=10), + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + + class UploadWorkspaceFormTest(TestCase): """Tests for the UploadWorkspace class.""" diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 5dd2bdfc..93e1a388 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -731,7 +731,7 @@ def test_has_form_in_context(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertTrue("form" in response.context_data) - self.assertIsInstance(response.context_data["form"], forms.UploadCycleForm) + self.assertIsInstance(response.context_data["form"], forms.UploadCycleCreateForm) def test_can_create_an_object(self): """Posting valid data to the form creates an object.""" diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 6790f5bb..191eb3d2 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -105,7 +105,7 @@ class UploadCycleCreate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageM """View to create a new UploadCycle object.""" model = models.UploadCycle - form_class = forms.UploadCycleForm + form_class = forms.UploadCycleCreateForm success_message = "Successfully created Upload Cycle." From d798e99a7cf8f1f4a2dbfff93d8f28e064ccd103 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 22 Aug 2024 16:40:01 -0700 Subject: [PATCH 061/125] Add clean check for date_ready_for_compute In the UploadCycle clean method, verify that date_ready_for_compute is between the start and end dates. --- gregor_django/gregor_anvil/models.py | 6 ++++ .../gregor_anvil/tests/test_models.py | 31 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 8cd5ffec..2e4499c2 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -187,6 +187,12 @@ def clean(self): # End date must be after start date. if self.start_date and self.end_date and self.start_date >= self.end_date: raise ValidationError("end_date must be after start_date!") + # date_ready_for_compute must be after start_date + if self.start_date and self.date_ready_for_compute and self.start_date > self.date_ready_for_compute: + raise ValidationError("date_ready_for_compute must be after start_date!") + # date_ready_for_compute must be before end_date + if self.end_date and self.date_ready_for_compute and self.end_date < self.date_ready_for_compute: + raise ValidationError("date_ready_for_compute must be before end_date!") def get_partner_upload_workspaces(self): """Return a queryset of PartnerUploadWorkspace objects that are included in this upload cycle. diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index 2273b3c0..21cb31c6 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -303,8 +303,39 @@ def test_start_date_equal_to_end_date(self): self.assertEqual(len(e.exception.message_dict), 1) self.assertIn(NON_FIELD_ERRORS, e.exception.message_dict) self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("end_date", e.exception.message_dict[NON_FIELD_ERRORS][0]) self.assertIn("after start_date", e.exception.message_dict[NON_FIELD_ERRORS][0]) + def test_start_date_after_date_ready_for_compute(self): + today = date.today() + instance = factories.UploadCycleFactory.build( + start_date=today + timedelta(days=1), + end_date=today + timedelta(days=10), + date_ready_for_compute=today, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.message_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.message_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("date_ready_for_compute", e.exception.message_dict[NON_FIELD_ERRORS][0]) + self.assertIn("after start_date", e.exception.message_dict[NON_FIELD_ERRORS][0]) + + def test_date_ready_for_compute_after_end_date(self): + today = date.today() + instance = factories.UploadCycleFactory.build( + start_date=today, + end_date=today + timedelta(days=10), + date_ready_for_compute=today + timedelta(days=11), + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.message_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.message_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("date_ready_for_compute", e.exception.message_dict[NON_FIELD_ERRORS][0]) + self.assertIn("before end_date", e.exception.message_dict[NON_FIELD_ERRORS][0]) + def test_get_partner_upload_workspaces_no_date_completed(self): """PartnerUploadWorkspace with no date_completed is not included.""" upload_cycle = factories.UploadCycleFactory.create() From 0db27a56aeb18a62e3a6a65bcbb5bf0d4f764db4 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 10:25:18 -0700 Subject: [PATCH 062/125] Add view to update an Upload Cycle --- gregor_django/gregor_anvil/forms.py | 14 +- .../gregor_anvil/tests/test_views.py | 162 ++++++++++++++++++ gregor_django/gregor_anvil/urls.py | 1 + gregor_django/gregor_anvil/views.py | 11 +- .../gregor_anvil/uploadcycle_form.html | 8 +- 5 files changed, 192 insertions(+), 4 deletions(-) diff --git a/gregor_django/gregor_anvil/forms.py b/gregor_django/gregor_anvil/forms.py index cf864f6e..f936330c 100644 --- a/gregor_django/gregor_anvil/forms.py +++ b/gregor_django/gregor_anvil/forms.py @@ -34,14 +34,24 @@ class UploadCycleCreateForm(UploadCycleForm): """Form to create an UploadCycle object.""" class Meta(UploadCycleForm.Meta): - exclude = ("date_ready_for_compute",) # noqa: DJ006 + fields = ( + "cycle", + "start_date", + "end_date", + "note", + ) class UploadCycleUpdateForm(UploadCycleForm): """Form to update an UploadCycle object.""" class Meta(UploadCycleForm.Meta): - exclude = ("cycle",) # noqa: DJ006 + fields = ( + "start_date", + "end_date", + "date_ready_for_compute", + "note", + ) class UploadWorkspaceForm(forms.ModelForm): diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 93e1a388..bde09aa8 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -834,6 +834,168 @@ def test_post_blank_data(self): self.assertEqual(models.UploadCycle.objects.count(), 0) +class UploadCycleUpdateTest(TestCase): + def setUp(self): + """Set up test class.""" + self.factory = RequestFactory() + # Create a user with both view and edit permissions. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + # Data for forms + self.start_date = date.today() + self.end_date = self.start_date + timedelta(days=10) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("gregor_anvil:upload_cycles:update", args=args) + + def get_view(self): + """Return the view being tested.""" + return views.UploadCycleUpdate.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url(1)) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) + + def test_status_code_with_user_permission(self): + """Returns successful response code.""" + obj = factories.UploadCycleFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertEqual(response.status_code, 200) + + def test_access_with_view_permission(self): + """Raises permission denied if user has only view permission.""" + user_with_view_perm = User.objects.create_user(username="test-other", password="test-other") + user_with_view_perm.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + request = self.factory.get(self.get_url(1)) + request.user = user_with_view_perm + with self.assertRaises(PermissionDenied): + self.get_view()(request, pk=1) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url(1)) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request, pk=1) + + def test_has_form_in_context(self): + """Response includes a form.""" + obj = factories.UploadCycleFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertTrue("form" in response.context_data) + self.assertIsInstance(response.context_data["form"], forms.UploadCycleUpdateForm) + + def test_can_update_an_object(self): + """Posting valid data to the form creates an object.""" + obj = factories.UploadCycleFactory.create(is_current=True) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.pk), + { + "start_date": obj.start_date, + "end_date": obj.end_date, + "date_ready_for_compute": timezone.localdate(), + "note": "", + }, + ) + self.assertEqual(response.status_code, 302) + self.assertEqual(models.UploadCycle.objects.count(), 1) + obj.refresh_from_db() + self.assertEqual(obj.date_ready_for_compute, timezone.localdate()) + # History is added. + self.assertEqual(obj.history.count(), 2) + self.assertEqual(obj.history.latest().history_type, "~") + + def test_success_message(self): + """Response includes a success message if successful.""" + obj = factories.UploadCycleFactory.create(is_current=True) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + { + "start_date": obj.start_date, + "end_date": obj.end_date, + "date_ready_for_compute": timezone.localdate(), + "note": "", + }, + ) + self.assertEqual(response.status_code, 302) + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertEqual(views.UploadCycleUpdate.success_message, str(messages[0])) + + def test_redirects_to_object_detail(self): + """After successfully creating an object, view redirects to the object's detail page.""" + obj = factories.UploadCycleFactory.create(is_current=True) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + { + "start_date": obj.start_date, + "end_date": obj.end_date, + "date_ready_for_compute": timezone.localdate(), + "note": "", + }, + ) + self.assertRedirects(response, obj.get_absolute_url()) + + def test_object_does_not_exist(self): + """Raises 404 when object doesn't exist.""" + request = self.factory.get(self.get_url(1)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()(request, pk=1) + + def test_invalid_input(self): + """Posting invalid data does not create an object.""" + obj = factories.UploadCycleFactory.create(is_current=True) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + {"start_date": self.start_date, "end_date": self.end_date, "date_ready_for_compute": "foo"}, + ) + self.assertEqual(response.status_code, 200) + form = response.context_data["form"] + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors.keys()), 1) + self.assertIn("date_ready_for_compute", form.errors.keys()) + self.assertEqual(len(form.errors["date_ready_for_compute"]), 1) + obj.refresh_from_db() + self.assertIsNone(obj.date_ready_for_compute) + + def test_post_blank_data_ready_for_compute(self): + """Can successfully post blank data for date_ready_for_compute.""" + obj = factories.UploadCycleFactory.create(is_current=True) + start_date = obj.start_date + end_date = obj.end_date + self.client.force_login(self.user) + response = self.client.post( + self.get_url(obj.cycle), + { + "start_date": start_date + timedelta(days=1), + "end_date": end_date + timedelta(days=1), + }, + ) + self.assertEqual(response.status_code, 302) + obj.refresh_from_db() + self.assertEqual(obj.start_date, start_date + timedelta(days=1)) + self.assertEqual(obj.end_date, end_date + timedelta(days=1)) + self.assertIsNone(obj.date_ready_for_compute) + + class UploadCycleDetailTest(TestCase): """Tests for the UploadCycle view.""" diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index eaad0a61..7bcc1f5a 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -33,6 +33,7 @@ [ path("/", views.UploadCycleDetail.as_view(), name="detail"), path("new/", views.UploadCycleCreate.as_view(), name="new"), + path("/update/", views.UploadCycleUpdate.as_view(), name="update"), path("", views.UploadCycleList.as_view(), name="list"), ], "upload_cycles", diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 191eb3d2..a4b53925 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -13,7 +13,7 @@ from django.forms import Form from django.http import Http404, HttpResponse from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, DetailView, FormView, TemplateView +from django.views.generic import CreateView, DetailView, FormView, TemplateView, UpdateView from django_tables2 import MultiTableMixin, SingleTableView from gregor_django.users.tables import UserTable @@ -109,6 +109,15 @@ class UploadCycleCreate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageM success_message = "Successfully created Upload Cycle." +class UploadCycleUpdate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, UpdateView): + """View to create a new UploadCycle object.""" + + model = models.UploadCycle + slug_field = "cycle" + form_class = forms.UploadCycleUpdateForm + success_message = "Successfully updated Upload Cycle." + + class UploadCycleDetail(AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView): """View to show details about an `UploadCycle`.""" diff --git a/gregor_django/templates/gregor_anvil/uploadcycle_form.html b/gregor_django/templates/gregor_anvil/uploadcycle_form.html index 59cfe860..5a2d3581 100644 --- a/gregor_django/templates/gregor_anvil/uploadcycle_form.html +++ b/gregor_django/templates/gregor_anvil/uploadcycle_form.html @@ -5,7 +5,13 @@ {% block content %} -

    Add a new Upload Cycle

    +

    + {% if object %} + Update Upload Cycle {{ object }} + {% else %} + Add a new Upload Cycle + {% endif %} +

    {% csrf_token %} From a999966f2e405ff7b98d6261af709db14e9f9619 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 11:01:51 -0700 Subject: [PATCH 063/125] Add link to update view on UploadCycle detail page --- gregor_django/gregor_anvil/tests/test_views.py | 17 +++++++++++++++++ .../gregor_anvil/uploadcycle_detail.html | 7 +++++++ 2 files changed, 24 insertions(+) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index bde09aa8..8de84ecb 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1154,6 +1154,23 @@ def test_link_to_audit(self): response = self.client.get(self.get_url(obj.cycle)) self.assertContains(response, reverse("gregor_anvil:audit:upload_workspaces:by_upload_cycle", args=[obj.cycle])) + def test_link_to_update_view_staff_edit(self): + """Response includes a link to the update view for staff edit users.""" + obj = self.model_factory.create() + self.user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + + def test_link_to_update_view_staff_view(self): + """Response includes a link to the update view for staff edit users.""" + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertNotContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + class UploadCycleListTest(TestCase): """Tests for the UploadCycleList view.""" diff --git a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html index 09ddb652..04a4f8c5 100644 --- a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html @@ -61,6 +61,13 @@

    Partner upload workspaces

    {% endblock after_panel %} {% block action_buttons %} +{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_edit %} +

    + + Update + +

    + {% endif %}

    Audit consortium sharing From c8de22d2d30c064d8f5f0f452f37368573bdd346 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 11:07:24 -0700 Subject: [PATCH 064/125] Add date_qc_completed to UploadWorkspaceForm --- gregor_django/gregor_anvil/forms.py | 4 ++++ gregor_django/gregor_anvil/tests/test_forms.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/gregor_django/gregor_anvil/forms.py b/gregor_django/gregor_anvil/forms.py index f936330c..45669303 100644 --- a/gregor_django/gregor_anvil/forms.py +++ b/gregor_django/gregor_anvil/forms.py @@ -63,8 +63,12 @@ class Meta: "research_center", "consent_group", "upload_cycle", + "date_qc_completed", "workspace", ) + widgets = { + "date_qc_completed": CustomDateInput(), + } class PartnerUploadWorkspaceForm(forms.ModelForm): diff --git a/gregor_django/gregor_anvil/tests/test_forms.py b/gregor_django/gregor_anvil/tests/test_forms.py index e29858f5..ba18c8f8 100644 --- a/gregor_django/gregor_anvil/tests/test_forms.py +++ b/gregor_django/gregor_anvil/tests/test_forms.py @@ -242,6 +242,21 @@ def test_invalid_duplicate_object(self): self.assertEqual(len(non_field_errors), 1) self.assertIn("already exists", non_field_errors[0]) + def test_valid_date_qc_completed(self): + """Form is invalid with a duplicated object.""" + research_center = factories.ResearchCenterFactory() + consent_group = factories.ConsentGroupFactory() + upload_cycle = factories.UploadCycleFactory(is_past=True) + form_data = { + "research_center": research_center, + "consent_group": consent_group, + "upload_cycle": upload_cycle, + "workspace": self.workspace, + "date_qc_completed": date.today(), + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + class PartnerUploadWorkspaceFormTest(TestCase): """Tests for the PartnerUploadWorkspace class.""" From bd9a53b1578065cad831c1de18afb7d465b2813f Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 11:09:29 -0700 Subject: [PATCH 065/125] Add date_completed to Combined workspace form --- gregor_django/gregor_anvil/forms.py | 4 ++++ gregor_django/gregor_anvil/tests/test_forms.py | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/gregor_django/gregor_anvil/forms.py b/gregor_django/gregor_anvil/forms.py index 45669303..e71531a3 100644 --- a/gregor_django/gregor_anvil/forms.py +++ b/gregor_django/gregor_anvil/forms.py @@ -118,7 +118,11 @@ class Meta: fields = ( "workspace", "upload_cycle", + "date_completed", ) + widgets = { + "date_completed": CustomDateInput(), + } class ReleaseWorkspaceForm(Bootstrap5MediaFormMixin, forms.ModelForm): diff --git a/gregor_django/gregor_anvil/tests/test_forms.py b/gregor_django/gregor_anvil/tests/test_forms.py index ba18c8f8..163fab15 100644 --- a/gregor_django/gregor_anvil/tests/test_forms.py +++ b/gregor_django/gregor_anvil/tests/test_forms.py @@ -450,10 +450,9 @@ def setUp(self): def test_invalid_missing_workspace(self): """Form is invalid when missing workspace.""" upload_cycle = factories.UploadCycleFactory.create() - upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=upload_cycle) form_data = { "upload_cycle": upload_cycle, - "upload_workspaces": [upload_workspace], + # "workspace": workspace, } form = self.form_class(data=form_data) self.assertFalse(form.is_valid()) @@ -474,6 +473,18 @@ def test_invalid_blank_upload_cycle(self): self.assertEqual(len(form.errors["upload_cycle"]), 1) self.assertIn("required", form.errors["upload_cycle"][0]) + def test_valid_date_qc_completed(self): + """Form is invalid with a duplicated object.""" + upload_cycle = factories.UploadCycleFactory(is_past=True) + workspace = WorkspaceFactory.create() + form_data = { + "upload_cycle": upload_cycle, + "workspace": workspace, + "date_completed": date.today(), + } + form = self.form_class(data=form_data) + self.assertTrue(form.is_valid()) + class ReleaseWorkspaceFormTest(TestCase): """Tests for the ReleaseWorkspace class.""" From 8ff1a4550fed76d5261c8be68b2aee1eb76b7c4c Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 11:23:55 -0700 Subject: [PATCH 066/125] Add checks on upload cycle dates and workspace date fields Add custom clean methods to check that date_qc_completed (for UploadWorkspaces) and date_completed (for Combined workspaces) is after the upload cycle end date. --- gregor_django/gregor_anvil/models.py | 12 +++++++++++ .../gregor_anvil/tests/test_models.py | 20 +++++++++++++++++++ .../gregor_anvil/tests/test_views.py | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 2e4499c2..ec0a4f85 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -258,6 +258,12 @@ class Meta: def __str__(self): return self.workspace.name + def clean(self): + """Custom cleaning methods.""" + # Check that date_qc_completed is after the upload cycle end date. + if self.date_qc_completed and self.upload_cycle.end_date > self.date_qc_completed: + raise ValidationError("date_qc_completed must after end_date of associated upload_cycle.") + class PartnerUploadWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track additional data about a partner workspace.""" @@ -311,6 +317,12 @@ class CombinedConsortiumDataWorkspace(TimeStampedModel, BaseWorkspaceData): default=None, ) + def clean(self): + """Custom cleaning methods.""" + # Check that date_qc_completed is after the upload cycle end date. + if self.date_completed and self.upload_cycle.end_date > self.date_completed: + raise ValidationError("date_completed must after end_date of associated upload_cycle.") + class ReleaseWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track a workspace for preparing data releases for the scientific community.""" diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index 21cb31c6..1c8b527e 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -606,6 +606,16 @@ def test_duplicated_workspace(self): with self.assertRaises(IntegrityError): instance_2.save() + def test_date_qc_completed_before_upload_cycle_end_date(self): + instance = factories.UploadWorkspaceFactory.create(upload_cycle__is_current=True) + instance.date_qc_completed = timezone.localdate() + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("after end_date", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + class PartnerGroupTest(TestCase): """Tests for the ResearchCenter model.""" @@ -867,6 +877,16 @@ def test_str_method(self): self.assertIsInstance(instance.__str__(), str) self.assertEqual(instance.__str__(), instance.workspace.__str__()) + def test_date_completed_before_upload_cycle_end_date(self): + instance = factories.CombinedConsortiumDataWorkspaceFactory.create(upload_cycle__is_current=True) + instance.date_completed = timezone.localdate() + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("after end_date", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + class ReleaseWorkspaceTest(TestCase): """Tests for the ReleaseWorkspace model.""" diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 8de84ecb..95889069 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -903,7 +903,7 @@ def test_can_update_an_object(self): obj = factories.UploadCycleFactory.create(is_current=True) self.client.force_login(self.user) response = self.client.post( - self.get_url(obj.pk), + self.get_url(obj.cycle), { "start_date": obj.start_date, "end_date": obj.end_date, From 88866a44b565e21619a8b27a5c665cd388d2acf9 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 13:22:21 -0700 Subject: [PATCH 067/125] Do not schedule combine-prs workflows It is better to review the dependabot PRs, close any that we do not want included in the combined update, and then run the combine-prs workflow manually (via workflow dispatch). This way we won't inadvertently get updates that we don't want (e.g., a passing PR that updates to django-5.0). --- .github/workflows/combine-prs.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/combine-prs.yml b/.github/workflows/combine-prs.yml index de7c331f..cf9c0d74 100644 --- a/.github/workflows/combine-prs.yml +++ b/.github/workflows/combine-prs.yml @@ -1,8 +1,6 @@ name: Combine PRs on: - schedule: - - cron: '0 8 * * 1' # Monday at 08:00 UTC workflow_dispatch: # allows you to manually trigger the workflow # The minimum permissions required to run this Action From 68e75d0d83ce63e83c870dbddc664510425d6a2d Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 14:12:39 -0700 Subject: [PATCH 068/125] Display new date fields in tables Add the UploadCycle date_ready_for_compute, UploadWorkspace date_qc_completed, and combined workspace date_completed to the tables in the app. --- gregor_django/gregor_anvil/tables.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gregor_django/gregor_anvil/tables.py b/gregor_django/gregor_anvil/tables.py index 1ba4f4ac..23ac7857 100644 --- a/gregor_django/gregor_anvil/tables.py +++ b/gregor_django/gregor_anvil/tables.py @@ -120,6 +120,7 @@ class Meta: "cycle", "start_date", "end_date", + "date_ready_for_compute", ) def render_cycle(self, record): @@ -188,6 +189,7 @@ class Meta: "uploadworkspace__upload_cycle", "uploadworkspace__research_center", "uploadworkspace__consent_group", + "uploadworkspace__date_qc_completed", "consortium_access", ) @@ -251,6 +253,7 @@ class Meta: fields = ( "name", "combinedconsortiumdataworkspace__upload_cycle", + "combinedconsortiumdataworkspace__date_completed", ) From b063f266b8ff408394c71f17b225140abaa776c1 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 14:29:17 -0700 Subject: [PATCH 069/125] Clean up upload_workspace_audit import block --- gregor_django/gregor_anvil/audit/upload_workspace_audit.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 285a4ee2..870120f2 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -7,8 +7,6 @@ from ..models import CombinedConsortiumDataWorkspace, UploadWorkspace from ..tables import BooleanIconColumn - -# from primed.primed_anvil.tables import BooleanIconColumn from .base import GREGoRAudit, GREGoRAuditResult From 7b538310c98ccd97a8ed847caf3aebe5fd15e439 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 14:35:31 -0700 Subject: [PATCH 070/125] Start writing the audit for upload workspace auth domains Add skeleton classes that will be used in and for the auditing. Start adding tests. --- .../upload_workspace_auth_domain_audit.py | 128 ++++++++++++++++++ .../gregor_anvil/tests/test_audit.py | 47 ++++++- 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py new file mode 100644 index 00000000..1fc11dc2 --- /dev/null +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -0,0 +1,128 @@ +from dataclasses import dataclass + +import django_tables2 as tables +from anvil_consortium_manager.models import GroupGroupMembership, ManagedGroup +from django.db.models import QuerySet + +from ..models import UploadWorkspace +from .base import GREGoRAudit, GREGoRAuditResult + + +@dataclass +class UploadWorkspaceAuthDomainAuditResult(GREGoRAuditResult): + """Base class to hold results for auditing upload workspace sharing.""" + + workspace: UploadWorkspace + note: str + managed_group: ManagedGroup + action: str = None + current_membership_instance: GroupGroupMembership = None + + def get_action_url(self): + """The URL that handles the action needed.""" + return "" + + def get_table_dictionary(self): + """Return a dictionary that can be used to populate an instance of `dbGaPDataSharingSnapshotAuditTable`.""" + row = { + "workspace": self.workspace, + "managed_group": self.managed_group, + "access": self.current_membership_instance.access if self.current_membership_instance else None, + "can_compute": self.current_membership_instance.can_compute if self.current_membership_instance else None, + "note": self.note, + "action": self.action, + "action_url": self.get_action_url(), + } + return row + + +@dataclass +class VerifiedMember(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when member membership has been verified.""" + + is_shared: bool = True + + def __str__(self): + return f"Verified member: {self.note}" + + +@dataclass +class VerifiedAdmin(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when membership with an admin role has been verified.""" + + is_shared: bool = False + + def __str__(self): + return f"Verified admin: {self.note}" + + +@dataclass +class ChangeToMember(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when an admin role should be changed to a member role.""" + + is_shared: bool = False + action: str = "Change to member" + + def __str__(self): + return f"Change to member: {self.note}" + + +@dataclass +class ChangeToAdmin(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when a member role should be changed to an admin role.""" + + is_shared: bool = False + action: str = "Change to admin" + + def __str__(self): + return f"Change to admin: {self.note}" + + +@dataclass +class Remove(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when group membership should be removed.""" + + is_shared: bool = False + action: str = "Share as owner" + + def __str__(self): + return f"Share as owner: {self.note}" + + +class UploadWorkspaceAuthDomainAuditTable(tables.Table): + """A table to show results from a UploadWorkspaceAuthDomainAudit subclass.""" + + workspace = tables.Column(linkify=True) + managed_group = tables.Column(linkify=True) + # is_shared = tables.Column() + role = tables.Column() + note = tables.Column() + # action = tables.Column() + action = tables.TemplateColumn( + template_name="gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html" + ) + + class Meta: + attrs = {"class": "table align-middle"} + + +class UploadWorkspaceAuthDomainAudit(GREGoRAudit): + """A class to hold audit results for the GREGoR UploadWorkspace auth domain audit.""" + + results_table_class = UploadWorkspaceAuthDomainAuditTable + + def __init__(self, queryset=None): + super().__init__() + if queryset is None: + queryset = UploadWorkspace.objects.all() + if not (isinstance(queryset, QuerySet) and queryset.model is UploadWorkspace): + raise ValueError("queryset must be a queryset of UploadWorkspace objects.") + self.queryset = queryset + + def _run_audit(self): + for workspace in self.queryset: + self.audit_upload_workspace(workspace) + + def audit_upload_workspace(self, upload_workspace): + """Audit the auth domain membership of a single UploadWorkspace.""" + pass diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 173db996..f0ac0a3e 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -9,7 +9,7 @@ from django.test import TestCase, override_settings from faker import Faker -from ..audit import upload_workspace_audit +from ..audit import upload_workspace_audit, upload_workspace_auth_domain_audit from ..audit.base import GREGoRAudit, GREGoRAuditResult from ..tests import factories @@ -3704,3 +3704,48 @@ def test_other_group_shared_as_owner(self): self.assertEqual(record.managed_group, self.other_group) self.assertEqual(record.current_sharing_instance, sharing) self.assertEqual(record.note, upload_workspace_audit.UploadWorkspaceAudit.OTHER_GROUP_NO_ACCESS) + + +class UploadWorkspaceAuthDomainAuditTest(TestCase): + """General tests of the `UploadWorkspaceAuthDomainAudit` class.""" + + def test_completed(self): + """The completed attribute is set appropriately.""" + # Instantiate the class. + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + self.assertFalse(audit.completed) + audit.run_audit() + self.assertTrue(audit.completed) + + def test_no_upload_workspaces(self): + """The audit works if there are no UploadWorkspaces.""" + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_no_groups(self): + upload_workspace = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + self.assertEqual(len(audit.queryset), 1) + self.assertIn(upload_workspace, audit.queryset) + + def test_two_upload_workspace_no_groups(self): + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + self.assertEqual(len(audit.queryset), 2) + self.assertIn(upload_workspace_1, audit.queryset) + self.assertIn(upload_workspace_2, audit.queryset) + + def test_finish_tests(self): + self.fail() From e430bf830ab57eb392271776eaa4376f0987e822 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 16:05:05 -0700 Subject: [PATCH 071/125] Add auth domain auditing for RC uploaders --- .../upload_workspace_auth_domain_audit.py | 148 ++++- .../gregor_anvil/tests/test_audit.py | 523 +++++++++++++++++- 2 files changed, 654 insertions(+), 17 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 1fc11dc2..31ef8822 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -2,9 +2,9 @@ import django_tables2 as tables from anvil_consortium_manager.models import GroupGroupMembership, ManagedGroup -from django.db.models import QuerySet +from django.db.models import Q, QuerySet -from ..models import UploadWorkspace +from ..models import CombinedConsortiumDataWorkspace, UploadWorkspace from .base import GREGoRAudit, GREGoRAuditResult @@ -18,20 +18,14 @@ class UploadWorkspaceAuthDomainAuditResult(GREGoRAuditResult): action: str = None current_membership_instance: GroupGroupMembership = None - def get_action_url(self): - """The URL that handles the action needed.""" - return "" - def get_table_dictionary(self): """Return a dictionary that can be used to populate an instance of `dbGaPDataSharingSnapshotAuditTable`.""" row = { "workspace": self.workspace, "managed_group": self.managed_group, - "access": self.current_membership_instance.access if self.current_membership_instance else None, - "can_compute": self.current_membership_instance.can_compute if self.current_membership_instance else None, + "role": self.current_membership_instance.role if self.current_membership_instance else None, "note": self.note, "action": self.action, - "action_url": self.get_action_url(), } return row @@ -40,8 +34,6 @@ def get_table_dictionary(self): class VerifiedMember(UploadWorkspaceAuthDomainAuditResult): """Audit results class for when member membership has been verified.""" - is_shared: bool = True - def __str__(self): return f"Verified member: {self.note}" @@ -56,11 +48,38 @@ def __str__(self): return f"Verified admin: {self.note}" +@dataclass +class VerifiedNotMember(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when member membership has been verified.""" + + def __str__(self): + return f"Verified member: {self.note}" + + +@dataclass +class AddMember(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when a member role should be added.""" + + action: str = "Add member" + + def __str__(self): + return f"Add member: {self.note}" + + +@dataclass +class AddAdmin(UploadWorkspaceAuthDomainAuditResult): + """Audit results class for when an admin role should be added.""" + + action: str = "Add admin" + + def __str__(self): + return f"Add admin: {self.note}" + + @dataclass class ChangeToMember(UploadWorkspaceAuthDomainAuditResult): """Audit results class for when an admin role should be changed to a member role.""" - is_shared: bool = False action: str = "Change to member" def __str__(self): @@ -71,7 +90,6 @@ def __str__(self): class ChangeToAdmin(UploadWorkspaceAuthDomainAuditResult): """Audit results class for when a member role should be changed to an admin role.""" - is_shared: bool = False action: str = "Change to admin" def __str__(self): @@ -82,7 +100,6 @@ def __str__(self): class Remove(UploadWorkspaceAuthDomainAuditResult): """Audit results class for when group membership should be removed.""" - is_shared: bool = False action: str = "Share as owner" def __str__(self): @@ -109,6 +126,17 @@ class Meta: class UploadWorkspaceAuthDomainAudit(GREGoRAudit): """A class to hold audit results for the GREGoR UploadWorkspace auth domain audit.""" + # Before combined workspace. + RC_BEFORE_COMBINED = ( + "RC uploader and member group should be members of the auth domain before the combined workspace is complete." + ) + + # After combined workspace. + RC_AFTER_COMBINED = ( + "RC uploader and member group should not be direct members of the auth domain" + " after the combined workspace is complete." + ) + results_table_class = UploadWorkspaceAuthDomainAuditTable def __init__(self, queryset=None): @@ -123,6 +151,96 @@ def _run_audit(self): for workspace in self.queryset: self.audit_upload_workspace(workspace) + def _get_current_membership(self, upload_workspace, managed_group): + try: + current_membership = GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=managed_group + ) + except GroupGroupMembership.DoesNotExist: + current_membership = None + return current_membership + + def _get_combined_workspace(self, upload_cycle): + """Returns the combined workspace, but only if it is ready for sharing.""" + try: + combined_workspace = CombinedConsortiumDataWorkspace.objects.get( + upload_cycle=upload_cycle, date_completed__isnull=False + ) + except CombinedConsortiumDataWorkspace.DoesNotExist: + combined_workspace = None + return combined_workspace + def audit_upload_workspace(self, upload_workspace): """Audit the auth domain membership of a single UploadWorkspace.""" - pass + research_center = upload_workspace.research_center + groups_to_audit = ManagedGroup.objects.filter( + # RC uploader group. + Q(research_center_of_uploaders=research_center) + ).distinct() + + for group in groups_to_audit: + self.audit_workspace_and_group(upload_workspace, group) + + def audit_workspace_and_group(self, upload_workspace, managed_group): + if managed_group.research_center_of_uploaders == upload_workspace.research_center: + self._audit_workspace_and_group_for_rc(upload_workspace, managed_group) + + def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): + combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) + membership = self._get_current_membership(upload_workspace, managed_group) + if not combined_workspace and not membership: + self.needs_action.append( + AddMember( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_BEFORE_COMBINED, + current_membership_instance=membership, + ) + ) + elif not combined_workspace and membership: + if membership.role == GroupGroupMembership.MEMBER: + self.verified.append( + VerifiedMember( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_BEFORE_COMBINED, + current_membership_instance=membership, + ) + ) + else: + self.errors.append( + ChangeToMember( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_BEFORE_COMBINED, + current_membership_instance=membership, + ) + ) + elif combined_workspace and not membership: + self.verified.append( + VerifiedNotMember( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_AFTER_COMBINED, + current_membership_instance=membership, + ) + ) + elif combined_workspace and membership: + if membership.role == "ADMIN": + self.errors.append( + Remove( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_AFTER_COMBINED, + current_membership_instance=membership, + ) + ) + else: + self.needs_action.append( + Remove( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_AFTER_COMBINED, + current_membership_instance=membership, + ) + ) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index f0ac0a3e..a6c6126c 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -3,8 +3,12 @@ from dataclasses import dataclass import django_tables2 as tables -from anvil_consortium_manager.models import WorkspaceGroupSharing -from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceGroupSharingFactory +from anvil_consortium_manager.models import GroupGroupMembership, WorkspaceGroupSharing +from anvil_consortium_manager.tests.factories import ( + GroupGroupMembershipFactory, + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) from django.conf import settings from django.test import TestCase, override_settings from faker import Faker @@ -3749,3 +3753,518 @@ def test_two_upload_workspace_no_groups(self): def test_finish_tests(self): self.fail() + + +class UploadWorkspaceAuthDomainAuditFutureCycleTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for future cycle UploadWorkspaces.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, upload_cycle__is_future=True + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + +class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_current=True, + upload_cycle__date_ready_for_compute=None, + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + +class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_current=True, + ) + # Set date ready for compute to a non-null value. + self.upload_workspace.upload_cycle.date_ready_for_compute = self.upload_workspace.upload_cycle.start_date + self.upload_workspace.upload_cycle.save() + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + +class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + - DCC writer group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + +class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. + + Expectations at this point in the upload cycle: + - RC uploader group should not have direct access. + - DCC writer group should not have direct access. + """ + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + date_qc_completed=fake.date_this_year(before_today=True, after_today=False), + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + +class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): + """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" + + def setUp(self): + super().setUp() + self.rc_uploader_group = ManagedGroupFactory.create() + self.rc_member_group = ManagedGroupFactory.create() + self.rc_non_member_group = ManagedGroupFactory.create() + self.dcc_member_group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + self.dcc_writer_group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.dcc_admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.gregor_all_group = ManagedGroupFactory.create(name="GREGOR_ALL") + self.research_center = factories.ResearchCenterFactory.create( + uploader_group=self.rc_uploader_group, + member_group=self.rc_member_group, + non_member_group=self.rc_non_member_group, + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create( + research_center=self.research_center, + upload_cycle__is_past=True, + date_qc_completed=fake.date_this_year(before_today=True, after_today=False), + ) + self.auth_domain = self.upload_workspace.workspace.authorization_domains.get() + # Create a corresponding combined workspace. + self.combined_workspace = factories.CombinedConsortiumDataWorkspaceFactory.create( + upload_cycle=self.upload_workspace.upload_cycle, + date_completed=fake.date_this_year( + before_today=True, + after_today=False, + ), + ) + self.other_group = ManagedGroupFactory.create() + self.anvil_admins = ManagedGroupFactory.create(name="anvil-admins") + self.anvil_devs = ManagedGroupFactory.create(name="anvil_devs") + + def test_rc_uploaders_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + ) + + def test_rc_uploaders_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + ) + + def test_rc_uploaders_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_uploader_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_uploader_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + ) From 0b5a778ba805919f6170fea54dceee8c36f818be Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 16:11:37 -0700 Subject: [PATCH 072/125] Add auditing for RC non-members --- .../upload_workspace_auth_domain_audit.py | 75 ++-- .../gregor_anvil/tests/test_audit.py | 330 ++++++++++++++++++ 2 files changed, 356 insertions(+), 49 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 31ef8822..5ace1904 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -137,6 +137,9 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): " after the combined workspace is complete." ) + # Non-member groups. + RC_NON_MEMBERS = "RC non-member group should always be a member of the auth domain." + results_table_class = UploadWorkspaceAuthDomainAuditTable def __init__(self, queryset=None): @@ -176,71 +179,45 @@ def audit_upload_workspace(self, upload_workspace): groups_to_audit = ManagedGroup.objects.filter( # RC uploader group. Q(research_center_of_uploaders=research_center) + | + # RC member group. + Q(research_center_of_members=research_center) ).distinct() for group in groups_to_audit: self.audit_workspace_and_group(upload_workspace, group) def audit_workspace_and_group(self, upload_workspace, managed_group): - if managed_group.research_center_of_uploaders == upload_workspace.research_center: + if managed_group == upload_workspace.research_center.uploader_group: + self._audit_workspace_and_group_for_rc(upload_workspace, managed_group) + elif managed_group == upload_workspace.research_center.member_group: self._audit_workspace_and_group_for_rc(upload_workspace, managed_group) def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) membership = self._get_current_membership(upload_workspace, managed_group) + if combined_workspace: + note = self.RC_AFTER_COMBINED + else: + note = self.RC_BEFORE_COMBINED + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + "note": note, + } + if not combined_workspace and not membership: - self.needs_action.append( - AddMember( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_BEFORE_COMBINED, - current_membership_instance=membership, - ) - ) + self.needs_action.append(AddMember(**result_kwargs)) elif not combined_workspace and membership: if membership.role == GroupGroupMembership.MEMBER: - self.verified.append( - VerifiedMember( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_BEFORE_COMBINED, - current_membership_instance=membership, - ) - ) + self.verified.append(VerifiedMember(**result_kwargs)) else: - self.errors.append( - ChangeToMember( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_BEFORE_COMBINED, - current_membership_instance=membership, - ) - ) + self.errors.append(ChangeToMember(**result_kwargs)) elif combined_workspace and not membership: - self.verified.append( - VerifiedNotMember( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_AFTER_COMBINED, - current_membership_instance=membership, - ) - ) + self.verified.append(VerifiedNotMember(**result_kwargs)) elif combined_workspace and membership: if membership.role == "ADMIN": - self.errors.append( - Remove( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_AFTER_COMBINED, - current_membership_instance=membership, - ) - ) + self.errors.append(Remove(**result_kwargs)) else: - self.needs_action.append( - Remove( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_AFTER_COMBINED, - current_membership_instance=membership, - ) - ) + self.needs_action.append(Remove(**result_kwargs)) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index a6c6126c..4b0f5678 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -3835,6 +3835,61 @@ def test_rc_uploaders_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" @@ -3918,6 +3973,61 @@ def test_rc_uploaders_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" @@ -4003,6 +4113,61 @@ def test_rc_uploaders_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -4090,6 +4255,61 @@ def test_rc_uploaders_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -4178,6 +4398,61 @@ def test_rc_uploaders_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" @@ -4268,3 +4543,58 @@ def test_rc_uploaders_admin(self): self.assertEqual( record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED ) + + def test_rc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + ) + + def test_rc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + ) + + def test_rc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + ) From 20adacf6aecb307b475b70c9a86c12c3a8ff8939 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 16:27:08 -0700 Subject: [PATCH 073/125] Add auditing for RC non-member groups --- .../upload_workspace_auth_domain_audit.py | 32 +++ .../gregor_anvil/tests/test_audit.py | 245 ++++++++++++++++++ 2 files changed, 277 insertions(+) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 5ace1904..96f4eca9 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -192,6 +192,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_group_for_rc(upload_workspace, managed_group) elif managed_group == upload_workspace.research_center.member_group: self._audit_workspace_and_group_for_rc(upload_workspace, managed_group) + elif managed_group == upload_workspace.research_center.non_member_group: + self._audit_workspace_and_group_for_rc_non_members(upload_workspace, managed_group) def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) @@ -221,3 +223,33 @@ def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): self.errors.append(Remove(**result_kwargs)) else: self.needs_action.append(Remove(**result_kwargs)) + + def _audit_workspace_and_group_for_rc_non_members(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + if not membership: + self.needs_action.append( + AddMember( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_NON_MEMBERS, + current_membership_instance=membership, + ) + ) + elif membership.role == GroupGroupMembership.MEMBER: + self.verified.append( + VerifiedMember( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_NON_MEMBERS, + current_membership_instance=membership, + ) + ) + else: + self.errors.append( + ChangeToMember( + workspace=upload_workspace, + managed_group=managed_group, + note=self.RC_NON_MEMBERS, + current_membership_instance=membership, + ) + ) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 4b0f5678..5124ed3a 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -3890,6 +3890,55 @@ def test_rc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" @@ -4028,6 +4077,55 @@ def test_rc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" @@ -4310,6 +4408,55 @@ def test_rc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -4453,6 +4600,55 @@ def test_rc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" @@ -4598,3 +4794,52 @@ def test_rc_members_admin(self): self.assertEqual( record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED ) + + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) From c5533109f28ec35acb562cae9a51b770b7efcca5 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 16:52:11 -0700 Subject: [PATCH 074/125] Add auditing for the DCC admins group --- .../upload_workspace_auth_domain_audit.py | 42 +- .../gregor_anvil/tests/test_audit.py | 384 ++++++++++++++++++ 2 files changed, 421 insertions(+), 5 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 96f4eca9..16f7510d 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -2,6 +2,7 @@ import django_tables2 as tables from anvil_consortium_manager.models import GroupGroupMembership, ManagedGroup +from django.conf import settings from django.db.models import Q, QuerySet from ..models import CombinedConsortiumDataWorkspace, UploadWorkspace @@ -126,20 +127,19 @@ class Meta: class UploadWorkspaceAuthDomainAudit(GREGoRAudit): """A class to hold audit results for the GREGoR UploadWorkspace auth domain audit.""" - # Before combined workspace. + # RC notes. RC_BEFORE_COMBINED = ( "RC uploader and member group should be members of the auth domain before the combined workspace is complete." ) - - # After combined workspace. RC_AFTER_COMBINED = ( "RC uploader and member group should not be direct members of the auth domain" " after the combined workspace is complete." ) - - # Non-member groups. RC_NON_MEMBERS = "RC non-member group should always be a member of the auth domain." + # DCC notes. + DCC_ADMINS = "DCC admin group should always be an admin of the auth domain." + results_table_class = UploadWorkspaceAuthDomainAuditTable def __init__(self, queryset=None): @@ -194,6 +194,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_group_for_rc(upload_workspace, managed_group) elif managed_group == upload_workspace.research_center.non_member_group: self._audit_workspace_and_group_for_rc_non_members(upload_workspace, managed_group) + elif managed_group.name == settings.ANVIL_DCC_ADMINS_GROUP_NAME: + self._audit_workspace_and_group_for_dcc_admin(upload_workspace, managed_group) def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) @@ -253,3 +255,33 @@ def _audit_workspace_and_group_for_rc_non_members(self, upload_workspace, manage current_membership_instance=membership, ) ) + + def _audit_workspace_and_group_for_dcc_admin(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + if not membership: + self.needs_action.append( + AddAdmin( + workspace=upload_workspace, + managed_group=managed_group, + note=self.DCC_ADMINS, + current_membership_instance=membership, + ) + ) + elif membership.role == GroupGroupMembership.ADMIN: + self.verified.append( + VerifiedAdmin( + workspace=upload_workspace, + managed_group=managed_group, + note=self.DCC_ADMINS, + current_membership_instance=membership, + ) + ) + else: + self.needs_action.append( + ChangeToAdmin( + workspace=upload_workspace, + managed_group=managed_group, + note=self.DCC_ADMINS, + current_membership_instance=membership, + ) + ) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 5124ed3a..f5fde68b 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -3939,6 +3939,70 @@ def test_rc_non_members_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" @@ -4126,6 +4190,70 @@ def test_rc_non_members_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" @@ -4266,6 +4394,70 @@ def test_rc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED ) + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -4457,6 +4649,70 @@ def test_rc_non_members_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -4649,6 +4905,70 @@ def test_rc_non_members_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" @@ -4843,3 +5163,67 @@ def test_rc_non_members_admin(self): self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + + def test_dcc_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_admins_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_admin_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foobar") + def test_dcc_admins_different_setting(self): + group = ManagedGroupFactory.create(name="foobar") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) From 105542103435b1f388ab51ea565b0e4dab0641c1 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 23 Aug 2024 16:58:14 -0700 Subject: [PATCH 075/125] Add auditing for GREGOR_DCC_WRITERS group --- .../upload_workspace_auth_domain_audit.py | 35 ++ .../gregor_anvil/tests/test_audit.py | 330 ++++++++++++++++++ 2 files changed, 365 insertions(+) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 16f7510d..79478c38 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -139,6 +139,10 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): # DCC notes. DCC_ADMINS = "DCC admin group should always be an admin of the auth domain." + DCC_BEFORE_COMBINED = "DCC groups should be a member of the auth domain before the combined workspace is complete." + DCC_AFTER_COMBINED = ( + "DCC groups should not be direct members of the auth domain after the combined workspace is complete." + ) results_table_class = UploadWorkspaceAuthDomainAuditTable @@ -196,6 +200,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_group_for_rc_non_members(upload_workspace, managed_group) elif managed_group.name == settings.ANVIL_DCC_ADMINS_GROUP_NAME: self._audit_workspace_and_group_for_dcc_admin(upload_workspace, managed_group) + elif managed_group.name == "GREGOR_DCC_WRITERS": + self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) @@ -285,3 +291,32 @@ def _audit_workspace_and_group_for_dcc_admin(self, upload_workspace, managed_gro current_membership_instance=membership, ) ) + + def _audit_workspace_and_group_for_dcc(self, upload_workspace, managed_group): + combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) + membership = self._get_current_membership(upload_workspace, managed_group) + if combined_workspace: + note = self.DCC_AFTER_COMBINED + else: + note = self.DCC_BEFORE_COMBINED + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + "note": note, + } + + if not combined_workspace and not membership: + self.needs_action.append(AddMember(**result_kwargs)) + elif not combined_workspace and membership: + if membership.role == GroupGroupMembership.MEMBER: + self.verified.append(VerifiedMember(**result_kwargs)) + else: + self.errors.append(ChangeToMember(**result_kwargs)) + elif combined_workspace and not membership: + self.verified.append(VerifiedNotMember(**result_kwargs)) + elif combined_workspace and membership: + if membership.role == "ADMIN": + self.errors.append(Remove(**result_kwargs)) + else: + self.needs_action.append(Remove(**result_kwargs)) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index f5fde68b..e96e453d 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -4003,6 +4003,61 @@ def test_dcc_admins_different_setting(self): self.assertIsNone(record.current_membership_instance) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" @@ -4254,6 +4309,61 @@ def test_dcc_admins_different_setting(self): self.assertIsNone(record.current_membership_instance) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" @@ -4458,6 +4568,61 @@ def test_dcc_admins_different_setting(self): self.assertIsNone(record.current_membership_instance) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -4713,6 +4878,61 @@ def test_dcc_admins_different_setting(self): self.assertIsNone(record.current_membership_instance) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -4969,6 +5189,61 @@ def test_dcc_admins_different_setting(self): self.assertIsNone(record.current_membership_instance) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" @@ -5227,3 +5502,58 @@ def test_dcc_admins_different_setting(self): self.assertEqual(record.managed_group, group) self.assertIsNone(record.current_membership_instance) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_dcc_writers_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_writers_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_writers_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_writer_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_writer_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) From 569aad34d7aec3eacc951a457bc10c1dc8cf93ce Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 26 Aug 2024 09:07:46 -0700 Subject: [PATCH 076/125] Add auth domain auditing for DCC members --- .../upload_workspace_auth_domain_audit.py | 2 + .../gregor_anvil/tests/test_audit.py | 330 ++++++++++++++++++ 2 files changed, 332 insertions(+) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 79478c38..04e7ce56 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -202,6 +202,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_group_for_dcc_admin(upload_workspace, managed_group) elif managed_group.name == "GREGOR_DCC_WRITERS": self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) + elif managed_group.name == "GREGOR_DCC_MEMBERS": + self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index e96e453d..6f5c40af 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -4058,6 +4058,61 @@ def test_dcc_writers_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" @@ -4364,6 +4419,61 @@ def test_dcc_writers_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" @@ -4623,6 +4733,61 @@ def test_dcc_writers_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -4933,6 +5098,61 @@ def test_dcc_writers_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -5244,6 +5464,61 @@ def test_dcc_writers_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" @@ -5557,3 +5832,58 @@ def test_dcc_writers_admin(self): self.assertEqual( record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED ) + + def test_dcc_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) + + def test_dcc_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.dcc_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.dcc_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.dcc_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED + ) From bd8943a7ba670babbd3682c5e96adb151a761f80 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 26 Aug 2024 09:20:34 -0700 Subject: [PATCH 077/125] Add auth domain auditing for GREGOR_ALL group --- .../upload_workspace_auth_domain_audit.py | 36 +- .../gregor_anvil/tests/test_audit.py | 330 ++++++++++++++++++ 2 files changed, 364 insertions(+), 2 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 04e7ce56..e7350385 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -136,6 +136,7 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): " after the combined workspace is complete." ) RC_NON_MEMBERS = "RC non-member group should always be a member of the auth domain." + GREGOR_ALL_BEFORE_COMBINED = "GREGOR_ALL should not have access before the combined workspace is complete." # DCC notes. DCC_ADMINS = "DCC admin group should always be an admin of the auth domain." @@ -143,6 +144,9 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): DCC_AFTER_COMBINED = ( "DCC groups should not be direct members of the auth domain after the combined workspace is complete." ) + GREGOR_ALL_AFTER_COMBINED = ( + "GREGOR_ALL should be a member of the auth domain after the combined workspace is complete." + ) results_table_class = UploadWorkspaceAuthDomainAuditTable @@ -204,6 +208,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) elif managed_group.name == "GREGOR_DCC_MEMBERS": self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) + elif managed_group.name == "GREGOR_ALL": + self._audit_workspace_and_group_for_gregor_all(upload_workspace, managed_group) def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) @@ -229,7 +235,7 @@ def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): elif combined_workspace and not membership: self.verified.append(VerifiedNotMember(**result_kwargs)) elif combined_workspace and membership: - if membership.role == "ADMIN": + if membership.role == GroupGroupMembership.ADMIN: self.errors.append(Remove(**result_kwargs)) else: self.needs_action.append(Remove(**result_kwargs)) @@ -318,7 +324,33 @@ def _audit_workspace_and_group_for_dcc(self, upload_workspace, managed_group): elif combined_workspace and not membership: self.verified.append(VerifiedNotMember(**result_kwargs)) elif combined_workspace and membership: - if membership.role == "ADMIN": + if membership.role == GroupGroupMembership.ADMIN: self.errors.append(Remove(**result_kwargs)) else: self.needs_action.append(Remove(**result_kwargs)) + + def _audit_workspace_and_group_for_gregor_all(self, upload_workspace, managed_group): + combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) + membership = self._get_current_membership(upload_workspace, managed_group) + if combined_workspace: + note = self.GREGOR_ALL_AFTER_COMBINED + else: + note = self.GREGOR_ALL_BEFORE_COMBINED + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + "note": note, + } + + if not combined_workspace and not membership: + self.verified.append(VerifiedNotMember(**result_kwargs)) + elif not combined_workspace and membership: + self.errors.append(Remove(**result_kwargs)) + elif combined_workspace and not membership: + self.needs_action.append(AddMember(**result_kwargs)) + elif combined_workspace and membership: + if membership.role == GroupGroupMembership.MEMBER: + self.verified.append(VerifiedMember(**result_kwargs)) + else: + self.errors.append(ChangeToMember(**result_kwargs)) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 6f5c40af..99baed7f 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -4113,6 +4113,61 @@ def test_dcc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" @@ -4474,6 +4529,61 @@ def test_dcc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" @@ -4788,6 +4898,61 @@ def test_dcc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -5153,6 +5318,61 @@ def test_dcc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -5519,6 +5739,61 @@ def test_dcc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED ) + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED + ) + class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" @@ -5887,3 +6162,58 @@ def test_dcc_members_admin(self): self.assertEqual( record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED ) + + def test_gregor_all_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_AFTER_COMBINED + ) + + def test_gregor_all_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_AFTER_COMBINED + ) + + def test_gregor_all_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.gregor_all_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.gregor_all_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.gregor_all_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_AFTER_COMBINED + ) From 54f00de33cc0cabb1188f6a33943a6200de7f585 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:25:28 +0000 Subject: [PATCH 078/125] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.6 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.6...v0.6.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ae1497f7..06d31928 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.5.6 + rev: v0.6.2 hooks: # Run the linter. - id: ruff From 72dc42c210b887fff0a2905bc3c885ceb691454b Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 26 Aug 2024 09:35:38 -0700 Subject: [PATCH 079/125] Add auth domain auditing for other groups --- .../upload_workspace_auth_domain_audit.py | 23 +- .../gregor_anvil/tests/test_audit.py | 294 ++++++++++++++++++ 2 files changed, 316 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index e7350385..9e7a588e 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -136,7 +136,6 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): " after the combined workspace is complete." ) RC_NON_MEMBERS = "RC non-member group should always be a member of the auth domain." - GREGOR_ALL_BEFORE_COMBINED = "GREGOR_ALL should not have access before the combined workspace is complete." # DCC notes. DCC_ADMINS = "DCC admin group should always be an admin of the auth domain." @@ -144,10 +143,16 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): DCC_AFTER_COMBINED = ( "DCC groups should not be direct members of the auth domain after the combined workspace is complete." ) + + # GREGOR_ALL notes. + GREGOR_ALL_BEFORE_COMBINED = "GREGOR_ALL should not have access before the combined workspace is complete." GREGOR_ALL_AFTER_COMBINED = ( "GREGOR_ALL should be a member of the auth domain after the combined workspace is complete." ) + # Other group notes. + OTHER_GROUP = "This group should not have access to the auth domain." + results_table_class = UploadWorkspaceAuthDomainAuditTable def __init__(self, queryset=None): @@ -210,6 +215,8 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) elif managed_group.name == "GREGOR_ALL": self._audit_workspace_and_group_for_gregor_all(upload_workspace, managed_group) + else: + self._audit_workspace_and_other_group(upload_workspace, managed_group) def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) @@ -354,3 +361,17 @@ def _audit_workspace_and_group_for_gregor_all(self, upload_workspace, managed_gr self.verified.append(VerifiedMember(**result_kwargs)) else: self.errors.append(ChangeToMember(**result_kwargs)) + + def _audit_workspace_and_other_group(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + "note": self.OTHER_GROUP, + } + + if not membership: + self.verified.append(VerifiedNotMember(**result_kwargs)) + elif membership: + self.errors.append(Remove(**result_kwargs)) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 99baed7f..24949743 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -4168,6 +4168,55 @@ def test_gregor_all_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED ) + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" @@ -4584,6 +4633,55 @@ def test_gregor_all_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED ) + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" @@ -4953,6 +5051,55 @@ def test_gregor_all_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED ) + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -5373,6 +5520,55 @@ def test_gregor_all_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED ) + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -5794,6 +5990,55 @@ def test_gregor_all_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_BEFORE_COMBINED ) + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" @@ -6217,3 +6462,52 @@ def test_gregor_all_admin(self): self.assertEqual( record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.GREGOR_ALL_AFTER_COMBINED ) + + def test_other_group_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_other_group_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.other_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.other_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.other_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) From 793eb9bd61a5130a2912f709445f40db4b6956c6 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 26 Aug 2024 09:42:51 -0700 Subject: [PATCH 080/125] Add auth domain auditing for various anvil groups In particular, anvil-admisn and anvil_devs --- .../upload_workspace_auth_domain_audit.py | 10 + .../gregor_anvil/tests/test_audit.py | 372 ++++++++++++++++++ 2 files changed, 382 insertions(+) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 9e7a588e..a040ee29 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -215,6 +215,10 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): self._audit_workspace_and_group_for_dcc(upload_workspace, managed_group) elif managed_group.name == "GREGOR_ALL": self._audit_workspace_and_group_for_gregor_all(upload_workspace, managed_group) + elif managed_group.name == "anvil-admins": + self._audit_workspace_and_anvil_group(upload_workspace, managed_group) + elif managed_group.name == "anvil_devs": + self._audit_workspace_and_anvil_group(upload_workspace, managed_group) else: self._audit_workspace_and_other_group(upload_workspace, managed_group) @@ -362,6 +366,12 @@ def _audit_workspace_and_group_for_gregor_all(self, upload_workspace, managed_gr else: self.errors.append(ChangeToMember(**result_kwargs)) + def _audit_workspace_and_anvil_group(self, upload_workspace, managed_group): + """Ignore the AnVIL groups in this audit. + + We don't want to make assumptions about what access level AnVIL has.""" + pass + def _audit_workspace_and_other_group(self, upload_workspace, managed_group): membership = self._get_current_membership(upload_workspace, managed_group) result_kwargs = { diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 24949743..cb224604 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -4217,6 +4217,68 @@ def test_other_group_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + class UploadWorkspaceAuthDomainAuditCurrentCycleBeforeComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces before compute.""" @@ -4682,6 +4744,68 @@ def test_other_group_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + class UploadWorkspaceAuthDomainAuditCurrentCycleAfterComputeTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for current cycle UploadWorkspaces after compute.""" @@ -5100,6 +5224,68 @@ def test_other_group_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + class UploadWorkspaceAuthDomainAuditPastCycleBeforeQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -5569,6 +5755,68 @@ def test_other_group_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + class UploadWorkspaceAuthDomainAuditPastCycleAfterQCCompleteTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles before QC is complete. @@ -6039,6 +6287,68 @@ def test_other_group_admin(self): self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + class UploadWorkspaceAuthDomainAuditPastCycleAfterCombinedWorkspaceSharedTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for past cycles after the combined workspace is complete.""" @@ -6511,3 +6821,65 @@ def test_other_group_admin(self): self.assertEqual(record.managed_group, self.other_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP) + + def test_anvil_admins_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_admins_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_admins, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_admins) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_member(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_anvil_devs_admin(self): + GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.anvil_devs, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.anvil_devs) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) From da42c4edc9206026316b3401ac43830cc2088340 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Mon, 26 Aug 2024 12:17:03 -0700 Subject: [PATCH 081/125] Updates to try to address request timeout issues with our drupal data sync * Increase timeout and retry count via jsonapi config * Change cron time from 2am to 3am in case there is a conflict --- gregor_apps.cron | 2 +- gregor_django/users/audit.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gregor_apps.cron b/gregor_apps.cron index 239f67c2..38bf78ad 100644 --- a/gregor_apps.cron +++ b/gregor_apps.cron @@ -9,4 +9,4 @@ MAILTO="gregorweb@uw.edu" 0 2 * * SUN . /var/www/django/gregor_apps/gregor-apps-activate.sh; python manage.py run_anvil_audit --email gregorconsortium@uw.edu >> cron.log # Nightly user data audit -0 2 * * * . /var/www/django/gregor_apps/gregor-apps-activate.sh; python manage.py sync-drupal-data --update --email gregorweb@uw.edu >> cron.log +0 3 * * * . /var/www/django/gregor_apps/gregor-apps-activate.sh; python manage.py sync-drupal-data --update --email gregorweb@uw.edu >> cron.log diff --git a/gregor_django/users/audit.py b/gregor_django/users/audit.py index 35fd5668..2a71d97c 100644 --- a/gregor_django/users/audit.py +++ b/gregor_django/users/audit.py @@ -619,6 +619,8 @@ def get_drupal_json_api(): "API_ROOT": api_root, "AUTH": OAuth2(client=client, client_id=json_api_client_id, token=token), "VALIDATE_SSL": True, + "TIMEOUT": 5, # default is 1 + "RETRIES": 5, # default is 3 } ) return drupal_api From 17ae13be6f8cccf27e65a68f4ac782145d373356 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 19:31:01 +0000 Subject: [PATCH 082/125] Bump django-constance from 3.1.0 to 4.0.1 Bumps [django-constance](https://github.com/jazzband/django-constance) from 3.1.0 to 4.0.1. - [Release notes](https://github.com/jazzband/django-constance/releases) - [Changelog](https://github.com/jazzband/django-constance/blob/master/docs/changes.rst) - [Commits](https://github.com/jazzband/django-constance/compare/3.1.0...4.0.1) --- updated-dependencies: - dependency-name: django-constance dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ed01a7a2..2c6b5989 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -55,7 +55,7 @@ django-anvil-consortium-manager @ git+https://github.com/UW-GAC/django-anvil-con # via -r requirements/requirements.in django-autocomplete-light==3.11.0 # via django-anvil-consortium-manager -django-constance==3.1.0 +django-constance==4.0.1 # via -r requirements/requirements.in django-crispy-forms==2.3 # via @@ -79,9 +79,7 @@ django-maintenance-mode==0.21.1 django-model-utils==4.5.1 # via -r requirements/requirements.in django-picklefield==3.2 - # via - # -r requirements/requirements.in - # django-constance + # via -r requirements/requirements.in django-simple-history==3.7.0 # via # -r requirements/requirements.in From fc50e1d7e55b933ae307fa1a5ee096fc01aa5d5f Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 26 Aug 2024 13:27:58 -0700 Subject: [PATCH 083/125] Add final group selection and tests for auth domain auditing --- .../upload_workspace_auth_domain_audit.py | 14 ++ .../gregor_anvil/tests/test_audit.py | 195 +++++++++++++++++- 2 files changed, 207 insertions(+), 2 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index a040ee29..7a3f6416 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -189,12 +189,26 @@ def _get_combined_workspace(self, upload_cycle): def audit_upload_workspace(self, upload_workspace): """Audit the auth domain membership of a single UploadWorkspace.""" research_center = upload_workspace.research_center + group_names = [ + "GREGOR_DCC_MEMBERS", + "GREGOR_DCC_WRITERS", + settings.ANVIL_DCC_ADMINS_GROUP_NAME, + ] groups_to_audit = ManagedGroup.objects.filter( # RC uploader group. Q(research_center_of_uploaders=research_center) | # RC member group. Q(research_center_of_members=research_center) + | + # RC non-member group. + Q(research_center_of_non_members=research_center) + | + # Other sepcific groups to include. + Q(name__in=group_names) + | + # Any other groups that are members. + Q(parent_memberships__parent_group=upload_workspace.workspace.authorization_domains.first()) ).distinct() for group in groups_to_audit: diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index cb224604..7066d18c 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -3751,8 +3751,199 @@ def test_two_upload_workspace_no_groups(self): self.assertIn(upload_workspace_1, audit.queryset) self.assertIn(upload_workspace_2, audit.queryset) - def test_finish_tests(self): - self.fail() + def test_one_upload_workspace_rc_member_group(self): + group = ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center__member_group=group, upload_cycle__is_future=True + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_rc_upload_group(self): + group = ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center__uploader_group=group, upload_cycle__is_future=True + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_rc_nonmember_group(self): + group = ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + research_center__non_member_group=group, upload_cycle__is_future=True + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_member_group(self): + group = ManagedGroupFactory.create(name="GREGOR_DCC_MEMBERS") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_writer_group(self): + group = ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_dcc_admin_group(self): + group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + @override_settings(ANVIL_DCC_ADMINS_GROUP_NAME="foo") + def test_one_upload_workspace_dcc_admin_group_different_name(self): + group = ManagedGroupFactory.create(name="foo") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_anvil_admin_group(self): + group = ManagedGroupFactory.create(name="anvil-admins") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_anvil_dev_group(self): + group = ManagedGroupFactory.create(name="anvil_devs") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + + def test_one_upload_workspace_gregor_all_group(self): + group = ManagedGroupFactory.create(name="GREGOR_ALL") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_other_group_member(self): + group = ManagedGroupFactory.create(name="foo") + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(record.workspace, upload_workspace) + self.assertEqual(record.managed_group, group) + + def test_one_upload_workspace_other_group_not_member(self): + ManagedGroupFactory.create(name="foo") + factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) class UploadWorkspaceAuthDomainAuditFutureCycleTest(TestCase): From 21963f06aa26507bd1b075a28046dc4533c58a8f Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 26 Aug 2024 13:42:23 -0700 Subject: [PATCH 084/125] Rework upload workspace sharing urls in prep for auth domain urls --- .../audit/upload_workspace_audit.py | 2 +- .../gregor_anvil/tests/test_views.py | 12 ++++---- gregor_django/gregor_anvil/urls.py | 28 ++++++++++++++++++- .../upload_workspace_audit_action_button.html | 4 +-- .../gregor_anvil/uploadcycle_detail.html | 2 +- .../gregor_anvil/uploadworkspace_detail.html | 2 +- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py index 870120f2..0d227937 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_audit.py @@ -23,7 +23,7 @@ class UploadWorkspaceAuditResult(GREGoRAuditResult): def get_action_url(self): """The URL that handles the action needed.""" # return reverse( - # "gregor_anvil:audit:upload_workspaces:resolve", + # "gregor_anvil:audit:upload_workspaces:sharing:resolve", # args=[ # self.dbgap_application.dbgap_project_id, # self.workspace.workspace.billing_project.name, diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 95889069..46b62c3d 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1152,7 +1152,9 @@ def test_link_to_audit(self): obj = self.model_factory.create() self.client.force_login(self.user) response = self.client.get(self.get_url(obj.cycle)) - self.assertContains(response, reverse("gregor_anvil:audit:upload_workspaces:by_upload_cycle", args=[obj.cycle])) + self.assertContains( + response, reverse("gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=[obj.cycle]) + ) def test_link_to_update_view_staff_edit(self): """Response includes a link to the update view for staff edit users.""" @@ -1303,7 +1305,7 @@ def test_contains_audit_consortium_access_button(self): self.client.force_login(self.user) response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) url = reverse( - "gregor_anvil:audit:upload_workspaces:by_upload_workspace", + "gregor_anvil:audit:upload_workspaces:sharing:by_upload_workspace", args=[ self.object.workspace.billing_project.name, self.object.workspace.name, @@ -2353,7 +2355,7 @@ def setUp(self): def get_url(self, *args): """Get the url for the view being tested.""" return reverse( - "gregor_anvil:audit:upload_workspaces:by_upload_workspace", + "gregor_anvil:audit:upload_workspaces:sharing:by_upload_workspace", args=args, ) @@ -2718,7 +2720,7 @@ def setUp(self): def get_url(self, *args): """Get the url for the view being tested.""" return reverse( - "gregor_anvil:audit:upload_workspaces:by_upload_cycle", + "gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=args, ) @@ -3052,7 +3054,7 @@ def setUp(self): def get_url(self, *args): """Get the url for the view being tested.""" return reverse( - "gregor_anvil:audit:upload_workspaces:resolve", + "gregor_anvil:audit:upload_workspaces:sharing:resolve", args=args, ) diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 7bcc1f5a..188ba9e3 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -46,7 +46,7 @@ "reports", ) -upload_workspace_audit_patterns = ( +upload_workspace_sharing_audit_patterns = ( [ # path("all/", views.UploadWorkspaceAuditAll.as_view(), name="all"), path( @@ -61,8 +61,34 @@ name="by_upload_workspace", ), ], + "sharing", +) + +# upload_workspace_auth_domain_audit_patterns = ( +# [ +# # path( +# # "resolve///", +# # views.UploadWorkspaceAuditResolve.as_view(), +# # name="resolve", +# # ), +# path("upload_cycle//", views.UploadWorkspaceAuditByUploadCycle.as_view(), name="by_upload_cycle"), +# path( +# "//", +# views.UploadWorkspaceAuditByWorkspace.as_view(), +# name="by_upload_workspace", +# ), +# ], +# "auth_domains", +# ) + +upload_workspace_audit_patterns = ( + [ + path("sharing/", include(upload_workspace_sharing_audit_patterns)), + # path("auth_domain/", include(upload_workspace_auth_domain_audit_patterns)), + ], "upload_workspaces", ) + audit_patterns = ( [ path("upload_workspaces/", include(upload_workspace_audit_patterns)), diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html index 06d55203..d0acfef9 100644 --- a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_audit_action_button.html @@ -2,13 +2,13 @@ {% if record.action %} + action="{% url 'gregor_anvil:audit:upload_workspaces:sharing:resolve' record.workspace.workspace.billing_project.name record.workspace.workspace.name record.managed_group.name %}"> {% csrf_token %}

    From f391c28ff8dcb35476ab99323c1ac5e7bbea6f28 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 26 Aug 2024 16:03:13 -0700 Subject: [PATCH 086/125] Add view to run auth domain audit for a single upload workspace --- .../upload_workspace_auth_domain_audit.py | 8 +- .../gregor_anvil/tests/test_views.py | 380 +++++++++++++++++- gregor_django/gregor_anvil/urls.py | 17 +- gregor_django/gregor_anvil/views.py | 40 +- ...rkspace_auth_domain_audit_explanation.html | 29 ++ .../upload_workspace_auth_domain_audit.html | 25 ++ 6 files changed, 490 insertions(+), 9 deletions(-) create mode 100644 gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html create mode 100644 gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit.html diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 7a3f6416..2026bec9 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -115,10 +115,10 @@ class UploadWorkspaceAuthDomainAuditTable(tables.Table): # is_shared = tables.Column() role = tables.Column() note = tables.Column() - # action = tables.Column() - action = tables.TemplateColumn( - template_name="gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html" - ) + action = tables.Column() + # action = tables.TemplateColumn( + # template_name="gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html" + # ) class Meta: attrs = {"class": "table align-middle"} diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 99f30ded..216f6621 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -25,7 +25,7 @@ from gregor_django.users.tests.factories import UserFactory from .. import forms, models, tables, views -from ..audit import upload_workspace_sharing_audit +from ..audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit from . import factories # from .utils import AnVILAPIMockTestMixin @@ -4169,3 +4169,381 @@ def test_post_new_share_as_writer_group_not_found_on_anvil_htmx(self): # No messages were added. messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(len(messages), 0) + + +class UploadWorkspaceAuthDomainAuditByWorkspaceTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceAuthDomainAuditByWorkspace view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + self.upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_workspace", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuthDomainAuditByWorkspace.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url("foo", "bar")) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar"), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()( + request, + billing_project_slug=self.upload_workspace.workspace.billing_project.name, + workspace_slug=self.upload_workspace.workspace.name, + ) + + def test_invalid_billing_project_name(self): + """Raises a 404 error with an invalid object billing project.""" + request = self.factory.get(self.get_url("foo", self.upload_workspace.workspace.name)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()( + request, + billing_project_slug="foo", + workspace_slug=self.upload_workspace.workspace.name, + ) + + def test_invalid_workspace_name(self): + """Raises a 404 error with an invalid workspace name.""" + request = self.factory.get(self.get_url(self.upload_workspace.workspace.billing_project.name, "foo")) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()( + request, + billing_project_slug=self.upload_workspace.workspace.billing_project.name, + workspace_slug="foo", + ) + + def test_context_audit_results(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(self.upload_workspace, audit_results.queryset) + + def test_context_audit_results_does_not_include_other_workspaces(self): + """The audit_results does not include other workspaces.""" + other_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + audit_results = response.context_data["audit_results"] + self.assertEqual(audit_results.queryset.count(), 1) + self.assertNotIn(other_workspace, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_verified_table_no_access(self): + """verified_table shows a record when audit has verifiednotmember.""" + self.upload_workspace.upload_cycle.start_date = timezone.localdate() - timedelta(days=20) + self.upload_workspace.upload_cycle.start_date = timezone.localdate() - timedelta(days=10) + self.upload_workspace.upload_cycle.save() + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=self.upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), None) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_member(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_admin(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_remove(self): + """needs_action_table shows a record when audit finds that access needs to be removed.""" + self.upload_workspace.upload_cycle.start_date = timezone.localdate() - timedelta(days=20) + self.upload_workspace.upload_cycle.start_date = timezone.localdate() - timedelta(days=10) + self.upload_workspace.upload_cycle.save() + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=self.upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_remove(self): + """error table shows a record when audit finds that access needs to be removed.""" + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_errors_table_change_to_member(self): + """needs action table shows a record when audit finds that access needs to be removed.""" + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_change_to_admin(self): + """error table shows a record when audit finds that access needs to be removed.""" + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + self.upload_workspace.workspace.billing_project.name, + self.upload_workspace.workspace.name, + ) + ) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index ac259fda..d79381f1 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -68,17 +68,28 @@ "sharing", ) -upload_workspace_sharing_audit_patterns = ( +upload_workspace_auth_domain_audit_patterns = ( + [ + path( + "//", + views.UploadWorkspaceAuthDomainAuditByWorkspace.as_view(), + name="by_upload_workspace", + ), + ], + "auth_domains", +) + +upload_workspace_audit_patterns = ( [ path("sharing/", include(upload_workspace_sharing_audit_patterns)), - # path("auth_domain/", include(upload_workspace_auth_domain_audit_patterns)), + path("auth_domain/", include(upload_workspace_auth_domain_audit_patterns)), ], "upload_workspaces", ) audit_patterns = ( [ - path("upload_workspaces/", include(upload_workspace_sharing_audit_patterns)), + path("upload_workspaces/", include(upload_workspace_audit_patterns)), ], "audit", ) diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index e402a6d3..78896730 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -19,7 +19,7 @@ from gregor_django.users.tables import UserTable from . import forms, models, tables -from .audit import upload_workspace_sharing_audit +from .audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit User = get_user_model() @@ -366,3 +366,41 @@ def form_valid(self, form): return HttpResponse(self.htmx_success) else: return super().form_valid(form) + + +class UploadWorkspaceAuthDomainAuditByWorkspace(AnVILConsortiumManagerStaffEditRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_auth_domain_audit.html" + model = models.UploadWorkspace + + def get_object(self, queryset=None): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + billing_project_slug = self.kwargs.get("billing_project_slug", None) + workspace_slug = self.kwargs.get("workspace_slug", None) + queryset = models.UploadWorkspace.objects.filter( + workspace__billing_project__name=billing_project_slug, + workspace__name=workspace_slug, + ) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=self.model.objects.filter(pk=self.object.pk) + ) + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html new file mode 100644 index 00000000..5243aa21 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html @@ -0,0 +1,29 @@ +
    +
    +

    + +

    +
    +
    + +

    + This audit checks that auth domain membership is appropriate for the current point in the upload cycle. + Membership for the following groups are checked: +

      +
    • GREGOR_DCC_ADMINS
    • +
    • GREGOR_DCC_WRITERS
    • +
    • GREGOR_DCC_MEMBERS
    • +
    • The upload group for the Research Center associated with this upload workspace
    • +
    • The member group for the Research Center associated with this upload workspace
    • +
    • The non-member group for the Research Center associated with this upload workspace
    • +
    • Any additional groups that the workspace is shared with
    • +
    + Note that groups associated with AnVIL (e.g., anvil-admins, anvil-devs) are ignored by the audit. +

    +

    Any errors should be reported!

    +
    +
    +
    +
    diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit.html b/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit.html new file mode 100644 index 00000000..1588d613 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit.html @@ -0,0 +1,25 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load django_tables2 %} + +{% block title %}Upload workspace auth domain audit{% endblock %} + + +{% block content %} + +

    Upload workspace auth domain audit: {{ object }}

    + +
    +

    + Auditing workspace auth domain membership for {{ object }}. + All records in the "Needs action" table should be handled by clicking on the action button. + Any records in the "Errors" table should be reported. +

    + {% include "gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html" %} + +
    + +

    Audit results

    + +{% include "__audit_tables.html" with verified_table=verified_table needs_action_table=needs_action_table errors_table=errors_table %} + +{% endblock content %} From b83bbba0d0a21a8177171b69a3912a9e064a1f28 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 13:46:38 -0700 Subject: [PATCH 087/125] Add a view to resolve auth domain audits --- .../gregor_anvil/tests/test_views.py | 1336 +++++++++++++++++ gregor_django/gregor_anvil/urls.py | 7 +- gregor_django/gregor_anvil/views.py | 122 +- ...rkspace_auth_domain_audit_explanation.html | 23 +- ...d_workspace_auth_domain_audit_resolve.html | 44 + ...pload_workspace_sharing_audit_resolve.html | 2 +- 6 files changed, 1523 insertions(+), 11 deletions(-) create mode 100644 gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit_resolve.html diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 216f6621..8de12849 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -4547,3 +4547,1339 @@ def test_context_needs_action_table_change_to_admin(self): upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, ) self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + +class UploadWorkspaceAuthDomainAuditResolveTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceAuthDomainAuditResolve view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:resolve", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuthDomainAuditResolve.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url("foo", "bar", "foobar")) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar", "foobar"), + ) + + def test_status_code_with_user_permission_staff_edit(self): + """Returns successful response code if the user has staff edit permission.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + + def test_status_code_with_user_permission_staff_view(self): + """Returns 403 response code if the user has staff view permission.""" + user_view = User.objects.create_user(username="test-view", password="test-view") + user_view.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(self.user) + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user_view + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_status_code_with_user_permission_view(self): + """Returns forbidden response code if the user has view permission.""" + user = User.objects.create_user(username="test-none", password="test-none") + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url("foo", "bar", "foobar")) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_get_billing_project_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url("foo", upload_workspace.workspace.name, group.name)) + self.assertEqual(response.status_code, 404) + + def test_get_workspace_name_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.workspace.billing_project.name, "foo", group.name)) + self.assertEqual(response.status_code, 404) + + def test_get_group_does_not_exist(self): + """get request raises a 404 error with an non-existent email.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + "foo", + ) + ) + self.assertEqual(response.status_code, 404) + + def test_get_context_audit_result(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + self.assertIsInstance( + response.context_data["audit_result"], + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditResult, + ) + + def test_get_verified_member(self): + """Get request with VerifiedMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_get_verified_admin(self): + """Get request with VerifiedAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS + ) + + def test_get_verified_not_member(self): + """Get request with VerifiedNotMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.VerifiedNotMember) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP + ) + + def test_get_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_get_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS + ) + + def test_get_change_to_member(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED + ) + + def test_get_change_to_admin(self): + """Get request with ChangeToAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.ChangeToAdmin) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS + ) + + def test_get_remove(self): + """Get request with ChangeToAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.client.force_login(self.user) + response = self.client.get( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertIn("audit_result", response.context_data) + audit_result = response.context_data["audit_result"] + self.assertIsInstance(audit_result, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(audit_result.workspace, upload_workspace) + self.assertEqual(audit_result.managed_group, group) + self.assertEqual( + audit_result.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP + ) + + def test_post_billing_project_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post(self.get_url("foo", upload_workspace.workspace.name, group.name)) + self.assertEqual(response.status_code, 404) + + def test_post_workspace_name_does_not_exist(self): + """Raises a 404 error with an invalid billing project.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post(self.get_url(upload_workspace.workspace.billing_project.name, "foo", group.name)) + self.assertEqual(response.status_code, 404) + + def test_post_group_does_not_exist(self): + """post request raises a 404 error with an non-existent email.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.post( + self.get_url( + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, + "foo", + ) + ) + self.assertEqual(response.status_code, 404) + + def test_post_verified_member(self): + """Get request with VerifiedMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + + def test_post_verified_admin(self): + """Get request with VerifiedAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + + def test_post_verified_not_member(self): + """Get request with VerifiedNotMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership = acm_models.GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + + def test_post_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership = acm_models.GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + def test_post_change_to_member(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertGreater(membership.modified, membership.created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + + def test_post_change_to_admin(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertGreater(membership.modified, membership.created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + def test_post_remove_admin(self): + """Post request with Remove result for an admin membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_remove_member(self): + """Post request with Remove result for a member membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertRedirects(response, upload_workspace.get_absolute_url()) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_htmx_verified_member(self): + """Get request with VerifiedMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + + def test_post_htmx_verified_admin(self): + """Get request with VerifiedAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + + def test_post_htmx_verified_not_member(self): + """Get request with VerifiedNotMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_htmx_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership = acm_models.GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + + def test_post_htmx_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership = acm_models.GroupGroupMembership.objects.get( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + ) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + def test_post_htmx_change_to_member(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertGreater(membership.modified, membership.created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + + def test_post_htmx_change_to_admin(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertGreater(membership.modified, membership.created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + + def test_post_htmx_remove_admin(self): + """Post request with Remove result for an admin membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_htmx_remove_member(self): + """Post request with Remove result for a member membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_success) + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + + def test_post_api_error_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No memberships were created. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # No memberships were created. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_change_to_member_error_on_put_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_change_to_member_error_on_delete_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_change_to_admin_error_on_delete_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_change_to_admin_error_on_put_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_remove_admin(self): + """Post request with Remove result for an admin membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_api_error_remove_member(self): + """Post request with Remove result for a member membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name) + ) + self.assertEqual(response.status_code, 200) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # Error message was added. + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 1) + self.assertIn("AnVIL API Error", str(messages[0])) + + def test_post_htmx_api_error_add_member(self): + """Get request with AddMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # No memberships were created. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_add_admin(self): + """Get request with AddAdmin result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + self.client.force_login(self.user) + # Add the mocked API response. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # No memberships were created. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 0) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_change_to_member_error_on_put_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=204, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_change_to_member_error_on_delete_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_change_to_admin_error_on_delete_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_change_to_admin_error_on_put_call(self): + """Get request with ChangeToMember result.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Add the mocked API responses - one to create and one to delete. + # Note that the auth domain group is created automatically by the factory using the workspace name. + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=204, + ) + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_remove_admin(self): + """Post request with Remove result for an admin membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/admin/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) + + def test_post_htmx_api_error_remove_member(self): + """Post request with Remove result for a member membership.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, workspace__name="test-ws" + ) + group = acm_factories.ManagedGroupFactory.create() + date_created = timezone.now() - timedelta(weeks=3) + with freeze_time(date_created): + membership = acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + self.anvil_response_mock.add( + responses.DELETE, + self.api_client.sam_entry_point + f"/api/groups/v1/auth_test-ws/member/{group.name}@firecloud.org", + status=500, + json=ErrorResponseFactory().response, + ) + self.client.force_login(self.user) + header = {"HTTP_HX-Request": "true"} + response = self.client.post( + self.get_url(upload_workspace.workspace.billing_project.name, upload_workspace.workspace.name, group.name), + **header, + ) + self.assertEqual(response.content.decode(), views.UploadWorkspaceAuthDomainAuditResolve.htmx_error) + # The membership was not updated. + self.assertEqual(acm_models.GroupGroupMembership.objects.count(), 1) + membership.refresh_from_db() + self.assertEqual(membership.created, date_created) + self.assertEqual(membership.modified, date_created) + self.assertEqual(membership.role, acm_models.GroupGroupMembership.MEMBER) + # No messages + messages = [m.message for m in get_messages(response.wsgi_request)] + self.assertEqual(len(messages), 0) diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index d79381f1..8e260949 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -50,7 +50,7 @@ [ # path("all/", views.UploadWorkspaceSharingAuditAll.as_view(), name="all"), path( - "resolve///", + "resolve////", views.UploadWorkspaceSharingAuditResolve.as_view(), name="resolve", ), @@ -70,6 +70,11 @@ upload_workspace_auth_domain_audit_patterns = ( [ + path( + "resolve////", + views.UploadWorkspaceAuthDomainAuditResolve.as_view(), + name="resolve", + ), path( "//", views.UploadWorkspaceAuthDomainAuditByWorkspace.as_view(), diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 78896730..5ddd052b 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -4,7 +4,13 @@ AnVILConsortiumManagerStaffViewRequired, ) from anvil_consortium_manager.exceptions import AnVILGroupNotFound -from anvil_consortium_manager.models import Account, ManagedGroup, Workspace, WorkspaceGroupSharing +from anvil_consortium_manager.models import ( + Account, + GroupGroupMembership, + ManagedGroup, + Workspace, + WorkspaceGroupSharing, +) from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.messages.views import SuccessMessageMixin @@ -404,3 +410,117 @@ def get_context_data(self, **kwargs): context["needs_action_table"] = audit.get_needs_action_table() context["audit_results"] = audit return context + + +class UploadWorkspaceAuthDomainAuditResolve(AnVILConsortiumManagerStaffEditRequired, FormView): + """View to resolve UploadWorkspace auth domain audit results.""" + + form_class = Form + template_name = "gregor_anvil/upload_workspace_auth_domain_audit_resolve.html" + htmx_success = """ Handled!""" + htmx_error = """ Error!""" + + def get_upload_workspace(self): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + billing_project_slug = self.kwargs.get("billing_project_slug", None) + workspace_slug = self.kwargs.get("workspace_slug", None) + queryset = models.UploadWorkspace.objects.filter( + workspace__billing_project__name=billing_project_slug, + workspace__name=workspace_slug, + ) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_managed_group(self, queryset=None): + """Look up the ManagedGroup by name.""" + try: + obj = ManagedGroup.objects.get(name=self.kwargs.get("managed_group_slug", None)) + except ManagedGroup.DoesNotExist: + raise Http404("No ManagedGroups found matching the query") + return obj + + def get_audit_result(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + # No way to set the group queryset, since it is dynamically determined by the workspace. + audit.audit_workspace_and_group(self.upload_workspace, self.managed_group) + # Set to completed, because we are just running this one specific check. + audit.completed = True + return audit.get_all_results()[0] + + def get(self, request, *args, **kwargs): + self.upload_workspace = self.get_upload_workspace() + self.managed_group = self.get_managed_group() + self.audit_result = self.get_audit_result() + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.upload_workspace = self.get_upload_workspace() + self.managed_group = self.get_managed_group() + self.audit_result = self.get_audit_result() + return super().post(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["upload_workspace"] = self.upload_workspace + context["managed_group"] = self.managed_group + context["audit_result"] = self.audit_result + return context + + def get_success_url(self): + return self.upload_workspace.get_absolute_url() + + def form_valid(self, form): + # Handle the result. + try: + with transaction.atomic(): + # Set up the membership instance. + if self.audit_result.current_membership_instance: + membership = self.audit_result.current_membership_instance + else: + membership = GroupGroupMembership( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.managed_group, + ) + # Now process the result. + if isinstance(self.audit_result, upload_workspace_auth_domain_audit.VerifiedMember): + pass + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.VerifiedAdmin): + pass + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.VerifiedNotMember): + pass + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.Remove): + membership.anvil_delete() + membership.delete() + else: + if isinstance(self.audit_result, upload_workspace_auth_domain_audit.ChangeToMember): + membership.anvil_delete() + membership.role = GroupGroupMembership.MEMBER + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.ChangeToAdmin): + membership.anvil_delete() + membership.role = GroupGroupMembership.ADMIN + else: + if isinstance(self.audit_result, upload_workspace_auth_domain_audit.AddMember): + membership.role = GroupGroupMembership.MEMBER + elif isinstance(self.audit_result, upload_workspace_auth_domain_audit.AddAdmin): + membership.role = GroupGroupMembership.ADMIN + membership.full_clean() + membership.save() + membership.anvil_create() + except (AnVILAPIError, AnVILGroupNotFound) as e: + if self.request.htmx: + return HttpResponse(self.htmx_error) + else: + messages.error(self.request, "AnVIL API Error: " + str(e)) + return super().form_invalid(form) + # Otherwise, the audit resolution succeeded. + if self.request.htmx: + return HttpResponse(self.htmx_success) + else: + return super().form_valid(form) diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html index 5243aa21..edd9af6c 100644 --- a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html @@ -10,15 +10,22 @@

    This audit checks that auth domain membership is appropriate for the current point in the upload cycle. - Membership for the following groups are checked: + Membership is expected to be as follows.

      -
    • GREGOR_DCC_ADMINS
    • -
    • GREGOR_DCC_WRITERS
    • -
    • GREGOR_DCC_MEMBERS
    • -
    • The upload group for the Research Center associated with this upload workspace
    • -
    • The member group for the Research Center associated with this upload workspace
    • -
    • The non-member group for the Research Center associated with this upload workspace
    • -
    • Any additional groups that the workspace is shared with
    • +
    • Before the combined workspace is completed:
    • +
        +
      • GREGOR_DCC_ADMINS (as admin)
      • +
      • GREGOR_DCC_WRITERS
      • +
      • GREGOR_DCC_MEMBERS
      • +
      • The upload group for the Research Center associated with this upload workspace
      • +
      • The member group for the Research Center associated with this upload workspace
      • +
      • The non-member group for the Research Center associated with this upload workspace
      • +
      +
    • After the combined workspace is completed:
    • +
        +
      • GREGOR_DCC_ADMINS (as admin)
      • +
      • GREGOR_ALL
      • +
    Note that groups associated with AnVIL (e.g., anvil-admins, anvil-devs) are ignored by the audit.

    diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit_resolve.html b/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit_resolve.html new file mode 100644 index 00000000..3f8270e3 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/upload_workspace_auth_domain_audit_resolve.html @@ -0,0 +1,44 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load django_tables2 %} +{% load crispy_forms_tags %} + +{% block title %}Resolve upload workspace audit{% endblock %} + + +{% block content %} + +

    Resolve upload workspace auth domain audit

    + +
    + + + {% include "gregor_anvil/snippets/upload_workspace_auth_domain_audit_explanation.html" %} + +
    + +

    Audit results

    + + +
    +
    +
    Result
    +

    {{ audit_result }}

    + {% if audit_result.action %} + + + {% csrf_token %} + {{ form|crispy }} + + + + {% else %} + + {% endif %} +
    +
    + + +{% endblock content %} diff --git a/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit_resolve.html b/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit_resolve.html index 5844752f..9f1b7a89 100644 --- a/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit_resolve.html +++ b/gregor_django/templates/gregor_anvil/upload_workspace_sharing_audit_resolve.html @@ -7,7 +7,7 @@ {% block content %} -

    Resolve upload workspace audit

    +

    Resolve upload workspace sharing audit

      From fb6d100f3d97db56e8885a9192d7b830ee7514c0 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 13:51:15 -0700 Subject: [PATCH 088/125] Use an htmx action button in the auth domain audit table This button resolves the audit when you click on it. --- .../upload_workspace_auth_domain_audit.py | 8 +++---- ...space_auth_domain_audit_action_button.html | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 2026bec9..7a3f6416 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -115,10 +115,10 @@ class UploadWorkspaceAuthDomainAuditTable(tables.Table): # is_shared = tables.Column() role = tables.Column() note = tables.Column() - action = tables.Column() - # action = tables.TemplateColumn( - # template_name="gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html" - # ) + # action = tables.Column() + action = tables.TemplateColumn( + template_name="gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html" + ) class Meta: attrs = {"class": "table align-middle"} diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html new file mode 100644 index 00000000..1e0437e9 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_auth_domain_audit_action_button.html @@ -0,0 +1,22 @@ +
      + {% if record.action %} +
      + + {% csrf_token %} + +
      + + {% else %} + — + {% endif %} +
      From 8b723344ff6359b0a1fda7b1ca515c57d5d9f2e3 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 13:55:25 -0700 Subject: [PATCH 089/125] Add link to auth domain audit on the UploadWorkspaceDetail view --- gregor_django/gregor_anvil/tests/test_views.py | 14 +++++++++++++- .../gregor_anvil/uploadworkspace_detail.html | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 8de12849..20fc37a4 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1301,7 +1301,7 @@ def test_status_code(self): response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) self.assertEqual(response.status_code, 200) - def test_contains_audit_consortium_access_button(self): + def test_contains_sharing_audit_button(self): self.client.force_login(self.user) response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) url = reverse( @@ -1313,6 +1313,18 @@ def test_contains_audit_consortium_access_button(self): ) self.assertContains(response, url) + def test_contains_auth_domain_audit_button(self): + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + url = reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertContains(response, url) + class UploadWorkspaceListTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" diff --git a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html index bfcb9540..f4b64f85 100644 --- a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html @@ -44,7 +44,7 @@

      Audit consortium sharing - + Audit auth domain membership

      From f056dd88b5d880d8645fcf0ee0c152f6788cd7b2 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 14:06:11 -0700 Subject: [PATCH 090/125] Add view to audit upload workspace auth domains by upload cycle --- .../gregor_anvil/tests/test_views.py | 341 ++++++++++++++++++ gregor_django/gregor_anvil/urls.py | 5 + gregor_django/gregor_anvil/views.py | 34 ++ 3 files changed, 380 insertions(+) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 20fc37a4..063cc72a 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -4561,6 +4561,347 @@ def test_context_needs_action_table_change_to_admin(self): self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") +class UploadWorkspaceAuthDomainAuditByUploadCycleTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceSharingAuditByUploadCycle view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.upload_cycle = factories.UploadCycleFactory.create(is_future=True) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuthDomainAuditByUploadCycle.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url(self.upload_cycle.cycle + 1)) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(self.upload_cycle.cycle + 1), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url(self.upload_cycle.cycle)) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request, cycle=self.upload_cycle.cycle) + + def test_invalid_upload_cycle(self): + """Raises a 404 error with an invalid upload cycle.""" + request = self.factory.get(self.get_url(self.upload_cycle.cycle + 1)) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()(request, cycle=self.upload_cycle.cycle + 1) + + def test_context_audit_results_no_upload_workspaces(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + + def test_context_audit_results_one_upload_workspace(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(upload_workspace, audit_results.queryset) + + def test_context_audit_results_two_upload_workspaces(self): + """The audit_results exists in the context.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + upload_workspace_2 = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 2) + self.assertIn(upload_workspace_1, audit_results.queryset) + self.assertIn(upload_workspace_2, audit_results.queryset) + + def test_context_audit_results_ignores_other_upload_cycles(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__cycle=self.upload_cycle.cycle + 1) + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.upload_cycle.cycle)) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + self.assertNotIn(upload_workspace, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_verified_table_no_access(self): + """verified_table shows a record when audit has verifiednotmember.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__start_date=timezone.localdate() - timedelta(days=20), + upload_cycle__end_date=timezone.localdate() - timedelta(days=10), + ) + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), None) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_member(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_admin(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_remove(self): + """needs_action_table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__start_date=timezone.localdate() - timedelta(days=20), + upload_cycle__end_date=timezone.localdate() - timedelta(days=10), + ) + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_remove(self): + """error table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_errors_table_change_to_member(self): + """needs action table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_change_to_admin(self): + """error table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle=self.upload_cycle) + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + class UploadWorkspaceAuthDomainAuditResolveTest(AnVILAPIMockTestMixin, TestCase): """Tests for the UploadWorkspaceAuthDomainAuditResolve view.""" diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 8e260949..59430294 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -75,6 +75,11 @@ views.UploadWorkspaceAuthDomainAuditResolve.as_view(), name="resolve", ), + path( + "upload_cycle//", + views.UploadWorkspaceAuthDomainAuditByUploadCycle.as_view(), + name="by_upload_cycle", + ), path( "//", views.UploadWorkspaceAuthDomainAuditByWorkspace.as_view(), diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 5ddd052b..108b767b 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -412,6 +412,40 @@ def get_context_data(self, **kwargs): return context +class UploadWorkspaceAuthDomainAuditByUploadCycle(AnVILConsortiumManagerStaffViewRequired, DetailView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_auth_domain_audit.html" + model = models.UploadCycle + + def get_object(self, queryset=None): + """Look up the UploadWorkspace by billing project and name.""" + # Filter the queryset based on kwargs. + cycle = self.kwargs.get("cycle", None) + queryset = self.model.objects.filter(cycle=cycle) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404( + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} + ) + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=models.UploadWorkspace.objects.filter(upload_cycle=self.object) + ) + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + class UploadWorkspaceAuthDomainAuditResolve(AnVILConsortiumManagerStaffEditRequired, FormView): """View to resolve UploadWorkspace auth domain audit results.""" From f880f7359b898a957e1cb0e38c2f9dfaa4e3dd88 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 14:09:34 -0700 Subject: [PATCH 091/125] Add link to auth domain audit view on UploadCycleDetail page --- gregor_django/gregor_anvil/tests/test_views.py | 14 ++++++++++++++ .../templates/gregor_anvil/uploadcycle_detail.html | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 063cc72a..0b3bbe38 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1173,6 +1173,20 @@ def test_link_to_update_view_staff_view(self): response = self.client.get(self.get_url(obj.cycle)) self.assertNotContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + def test_contains_sharing_audit_button(self): + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + url = reverse("gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=[obj.cycle]) + self.assertContains(response, url) + + def test_contains_auth_domain_audit_button(self): + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + url = reverse("gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", args=[obj.cycle]) + self.assertContains(response, url) + class UploadCycleListTest(TestCase): """Tests for the UploadCycleList view.""" diff --git a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html index f96ee00e..b8683f76 100644 --- a/gregor_django/templates/gregor_anvil/uploadcycle_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadcycle_detail.html @@ -72,7 +72,7 @@

      Partner upload workspaces

      Audit consortium sharing - + Audit auth domain membership

      From d2dc32041169410a4787b993b40d7d123ecca533 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 14:12:07 -0700 Subject: [PATCH 092/125] Clean up audit explanation --- .../snippets/upload_workspace_sharing_audit_explanation.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html index d53023d6..af07d58f 100644 --- a/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html +++ b/gregor_django/templates/gregor_anvil/snippets/upload_workspace_sharing_audit_explanation.html @@ -31,12 +31,12 @@

    • Needs action includes the following:
      • -
      • XXX
      • +
      • The access level for a specific group needs to be changed.
    • Errors
      • -
      • XXX
      • +
      • The workspace has been shared with an unexpected group as an "OWNER".

    From bce0be155a0f70437397d76f3f4e9df7a830ebbb Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 14:18:37 -0700 Subject: [PATCH 093/125] Add GREGOR_ALL to groups checked in auth domain audit There was a subfunction to audit the GREGOR_ALL group, but that group was not being automatically included in the audit for a workspace. Add it to the list of special groups to include. --- .../audit/upload_workspace_auth_domain_audit.py | 1 + gregor_django/gregor_anvil/tests/test_audit.py | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 7a3f6416..5733a783 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -190,6 +190,7 @@ def audit_upload_workspace(self, upload_workspace): """Audit the auth domain membership of a single UploadWorkspace.""" research_center = upload_workspace.research_center group_names = [ + "GREGOR_ALL", "GREGOR_DCC_MEMBERS", "GREGOR_DCC_WRITERS", settings.ANVIL_DCC_ADMINS_GROUP_NAME, diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index c790406b..479b4b86 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -3985,16 +3985,13 @@ def test_one_upload_workspace_anvil_dev_group(self): def test_one_upload_workspace_gregor_all_group(self): group = ManagedGroupFactory.create(name="GREGOR_ALL") upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) - GroupGroupMembershipFactory.create( - parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group - ) audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.run_audit() - self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.verified), 1) self.assertEqual(len(audit.needs_action), 0) - self.assertEqual(len(audit.errors), 1) - record = audit.errors[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) self.assertEqual(record.workspace, upload_workspace) self.assertEqual(record.managed_group, group) From f9647d9a261e7630df08c32ea89b240730806deb Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 14:20:16 -0700 Subject: [PATCH 094/125] Fix action text for Remove audit result --- .../gregor_anvil/audit/upload_workspace_auth_domain_audit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 5733a783..88b171a9 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -101,7 +101,7 @@ def __str__(self): class Remove(UploadWorkspaceAuthDomainAuditResult): """Audit results class for when group membership should be removed.""" - action: str = "Share as owner" + action: str = "Remove" def __str__(self): return f"Share as owner: {self.note}" From f5daea3b9cf579ea547bff2cc8d21fce8bc26d87 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 14:43:11 -0700 Subject: [PATCH 095/125] Add "current" to access and role columns This is a better indicating that the access and role that are shown are the current access/role, not the access/role that they should be. --- .../gregor_anvil/audit/upload_workspace_auth_domain_audit.py | 2 +- .../gregor_anvil/audit/upload_workspace_sharing_audit.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 88b171a9..9e99580f 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -113,7 +113,7 @@ class UploadWorkspaceAuthDomainAuditTable(tables.Table): workspace = tables.Column(linkify=True) managed_group = tables.Column(linkify=True) # is_shared = tables.Column() - role = tables.Column() + role = tables.Column(verbose_name="Current role") note = tables.Column() # action = tables.Column() action = tables.TemplateColumn( diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py index 4a121c92..e17939e6 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py @@ -134,7 +134,7 @@ class UploadWorkspaceSharingAuditTable(tables.Table): workspace = tables.Column(linkify=True) managed_group = tables.Column(linkify=True) # is_shared = tables.Column() - access = tables.Column() + access = tables.Column(verbose_name="Current access") can_compute = BooleanIconColumn(show_false_icon=True, null=True) note = tables.Column() # action = tables.Column() From 695694817259d4592c48d9c5c7fd2d983e7e0da9 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 14:44:11 -0700 Subject: [PATCH 096/125] Add auth domain membership to test data script Like the workspace sharing, create auth domain memberships such as they would be at the previous step of the cycle. --- add_upload_workspace_audit_test_data.py | 160 +++++++++++++++++++++++- 1 file changed, 159 insertions(+), 1 deletion(-) diff --git a/add_upload_workspace_audit_test_data.py b/add_upload_workspace_audit_test_data.py index 01ed9ceb..704c19de 100644 --- a/add_upload_workspace_audit_test_data.py +++ b/add_upload_workspace_audit_test_data.py @@ -1,5 +1,6 @@ -from anvil_consortium_manager.models import WorkspaceGroupSharing +from anvil_consortium_manager.models import GroupGroupMembership, WorkspaceGroupSharing from anvil_consortium_manager.tests.factories import ( + GroupGroupMembershipFactory, ManagedGroupFactory, WorkspaceGroupSharingFactory, ) @@ -11,9 +12,11 @@ # Create groups involved in the audit. dcc_admin_group = ManagedGroupFactory(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) dcc_writer_group = ManagedGroupFactory(name="GREGOR_DCC_WRITERS") +dcc_member_group = ManagedGroupFactory(name="GREGOR_DCC_MEMBERS") rc_1_member_group = ManagedGroupFactory(name="DEMO_RC1_MEMBERS") rc_1_uploader_group = ManagedGroupFactory(name="DEMO_RC1_UPLOADERS") rc_1_nonmember_group = ManagedGroupFactory(name="DEMO_RC1_NONMEMBERS") +gregor_all_group = ManagedGroupFactory(name="GREGOR_ALL") # Create an RC rc = factories.ResearchCenterFactory.create( @@ -75,6 +78,37 @@ access=WorkspaceGroupSharing.WRITER, can_compute=False, ) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) # Create a current upload cycle after compute. @@ -118,6 +152,37 @@ access=WorkspaceGroupSharing.WRITER, can_compute=False, ) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) # Create a past upload cycle before qc is completed. upload_cycle = factories.UploadCycleFactory.create( @@ -159,6 +224,37 @@ access=WorkspaceGroupSharing.WRITER, can_compute=True, ) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) # Create a past upload cycle after QC is completed. upload_cycle = factories.UploadCycleFactory.create( @@ -193,6 +289,37 @@ access=WorkspaceGroupSharing.WRITER, can_compute=True, ) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) # Create a past upload cycle with a combined workspace. upload_cycle = factories.UploadCycleFactory.create( @@ -225,3 +352,34 @@ access=WorkspaceGroupSharing.OWNER, can_compute=True, ) +# Create auth domain membership as appropriate. +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_nonmember_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=rc_1_uploader_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_member_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_writer_group, + role=GroupGroupMembership.MEMBER, +) +GroupGroupMembershipFactory.create( + parent_group=workspace.workspace.authorization_domains.first(), + child_group=dcc_admin_group, + role=GroupGroupMembership.ADMIN, +) From 3e627d1fb21975633a54993246391a64ed2195c6 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 15:03:45 -0700 Subject: [PATCH 097/125] Compress branch migrations into one Remove the previous migrations added into this branch and regenerate them, with all changes in a single migration. --- .../0027_tracking_fields_for_custom_audits.py | 100 ++++++++++++++++++ .../0027_uploadcycle_is_ready_for_compute.py | 23 ---- .../0028_uploadworkspace_date_qc_complete.py | 23 ---- ...iumcombineddataworkspace_date_completed.py | 23 ---- .../0030_researchcenter_non_member_group.py | 25 ----- ...dy_to_compute_to_date_ready_for_compute.py | 31 ------ ...oadworkspace_date_qc_completed_and_more.py | 23 ---- ...umdataworkspace_date_completed_and_more.py | 23 ---- 8 files changed, 100 insertions(+), 171 deletions(-) create mode 100644 gregor_django/gregor_anvil/migrations/0027_tracking_fields_for_custom_audits.py delete mode 100644 gregor_django/gregor_anvil/migrations/0027_uploadcycle_is_ready_for_compute.py delete mode 100644 gregor_django/gregor_anvil/migrations/0028_uploadworkspace_date_qc_complete.py delete mode 100644 gregor_django/gregor_anvil/migrations/0029_consortiumcombineddataworkspace_date_completed.py delete mode 100644 gregor_django/gregor_anvil/migrations/0030_researchcenter_non_member_group.py delete mode 100644 gregor_django/gregor_anvil/migrations/0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute.py delete mode 100644 gregor_django/gregor_anvil/migrations/0032_alter_historicaluploadworkspace_date_qc_completed_and_more.py delete mode 100644 gregor_django/gregor_anvil/migrations/0033_alter_combinedconsortiumdataworkspace_date_completed_and_more.py diff --git a/gregor_django/gregor_anvil/migrations/0027_tracking_fields_for_custom_audits.py b/gregor_django/gregor_anvil/migrations/0027_tracking_fields_for_custom_audits.py new file mode 100644 index 00000000..98879a4d --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0027_tracking_fields_for_custom_audits.py @@ -0,0 +1,100 @@ +# Generated by Django 4.2.15 on 2024-08-27 21:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("anvil_consortium_manager", "0019_accountuserarchive"), + ("gregor_anvil", "0026_historicalpartnergroup_drupal_node_id_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="combinedconsortiumdataworkspace", + name="date_completed", + field=models.DateField( + blank=True, + default=None, + help_text="Date that data preparation in this workspace was completed.", + null=True, + ), + ), + migrations.AddField( + model_name="historicalcombinedconsortiumdataworkspace", + name="date_completed", + field=models.DateField( + blank=True, + default=None, + help_text="Date that data preparation in this workspace was completed.", + null=True, + ), + ), + migrations.AddField( + model_name="historicalresearchcenter", + name="non_member_group", + field=models.ForeignKey( + blank=True, + db_constraint=False, + help_text="The AnVIL group containing non-members from this Research Center.", + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="anvil_consortium_manager.managedgroup", + ), + ), + migrations.AddField( + model_name="historicaluploadcycle", + name="date_ready_for_compute", + field=models.DateField( + blank=True, + default=None, + help_text="Date that this workspace was ready for RC uploaders to run compute.", + null=True, + ), + ), + migrations.AddField( + model_name="historicaluploadworkspace", + name="date_qc_completed", + field=models.DateField( + blank=True, + default=None, + help_text="Date that QC was completed for this workspace. If null, QC is not complete.", + null=True, + ), + ), + migrations.AddField( + model_name="researchcenter", + name="non_member_group", + field=models.OneToOneField( + blank=True, + help_text="The AnVIL group containing non-members from this Research Center.", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="research_center_of_non_members", + to="anvil_consortium_manager.managedgroup", + ), + ), + migrations.AddField( + model_name="uploadcycle", + name="date_ready_for_compute", + field=models.DateField( + blank=True, + default=None, + help_text="Date that this workspace was ready for RC uploaders to run compute.", + null=True, + ), + ), + migrations.AddField( + model_name="uploadworkspace", + name="date_qc_completed", + field=models.DateField( + blank=True, + default=None, + help_text="Date that QC was completed for this workspace. If null, QC is not complete.", + null=True, + ), + ), + ] diff --git a/gregor_django/gregor_anvil/migrations/0027_uploadcycle_is_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0027_uploadcycle_is_ready_for_compute.py deleted file mode 100644 index 9030988c..00000000 --- a/gregor_django/gregor_anvil/migrations/0027_uploadcycle_is_ready_for_compute.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-15 20:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gregor_anvil', '0026_historicalpartnergroup_drupal_node_id_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='historicaluploadcycle', - name='is_ready_for_compute', - field=models.BooleanField(default=False, help_text='Boolean indicator of whether workspace writers should be able to run compute.'), - ), - migrations.AddField( - model_name='uploadcycle', - name='is_ready_for_compute', - field=models.BooleanField(default=False, help_text='Boolean indicator of whether workspace writers should be able to run compute.'), - ), - ] diff --git a/gregor_django/gregor_anvil/migrations/0028_uploadworkspace_date_qc_complete.py b/gregor_django/gregor_anvil/migrations/0028_uploadworkspace_date_qc_complete.py deleted file mode 100644 index c9863efd..00000000 --- a/gregor_django/gregor_anvil/migrations/0028_uploadworkspace_date_qc_complete.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-15 23:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gregor_anvil', '0027_uploadcycle_is_ready_for_compute'), - ] - - operations = [ - migrations.AddField( - model_name='historicaluploadworkspace', - name='date_qc_completed', - field=models.DateTimeField(blank=True, default=None, help_text='Date that QC was completed for this workspace. If null, QC is not complete.', null=True), - ), - migrations.AddField( - model_name='uploadworkspace', - name='date_qc_completed', - field=models.DateTimeField(blank=True, default=None, help_text='Date that QC was completed for this workspace. If null, QC is not complete.', null=True), - ), - ] diff --git a/gregor_django/gregor_anvil/migrations/0029_consortiumcombineddataworkspace_date_completed.py b/gregor_django/gregor_anvil/migrations/0029_consortiumcombineddataworkspace_date_completed.py deleted file mode 100644 index 1c200d87..00000000 --- a/gregor_django/gregor_anvil/migrations/0029_consortiumcombineddataworkspace_date_completed.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-19 22:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gregor_anvil', '0028_uploadworkspace_date_qc_complete'), - ] - - operations = [ - migrations.AddField( - model_name='combinedconsortiumdataworkspace', - name='date_completed', - field=models.DateTimeField(blank=True, default=None, help_text='Date that data preparation in this workspace was completed.', null=True), - ), - migrations.AddField( - model_name='historicalcombinedconsortiumdataworkspace', - name='date_completed', - field=models.DateTimeField(blank=True, default=None, help_text='Date that data preparation in this workspace was completed.', null=True), - ), - ] diff --git a/gregor_django/gregor_anvil/migrations/0030_researchcenter_non_member_group.py b/gregor_django/gregor_anvil/migrations/0030_researchcenter_non_member_group.py deleted file mode 100644 index 6707a751..00000000 --- a/gregor_django/gregor_anvil/migrations/0030_researchcenter_non_member_group.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-20 19:12 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('anvil_consortium_manager', '0019_accountuserarchive'), - ('gregor_anvil', '0029_consortiumcombineddataworkspace_date_completed'), - ] - - operations = [ - migrations.AddField( - model_name='historicalresearchcenter', - name='non_member_group', - field=models.ForeignKey(blank=True, db_constraint=False, help_text='The AnVIL group containing non-members from this Research Center.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.managedgroup'), - ), - migrations.AddField( - model_name='researchcenter', - name='non_member_group', - field=models.OneToOneField(blank=True, help_text='The AnVIL group containing non-members from this Research Center.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='research_center_of_non_members', to='anvil_consortium_manager.managedgroup'), - ), - ] diff --git a/gregor_django/gregor_anvil/migrations/0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute.py deleted file mode 100644 index 309c19e5..00000000 --- a/gregor_django/gregor_anvil/migrations/0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-22 21:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gregor_anvil', '0030_researchcenter_non_member_group'), - ] - - operations = [ - migrations.RemoveField( - model_name='historicaluploadcycle', - name='is_ready_for_compute', - ), - migrations.RemoveField( - model_name='uploadcycle', - name='is_ready_for_compute', - ), - migrations.AddField( - model_name='historicaluploadcycle', - name='date_ready_for_compute', - field=models.DateField(blank=True, default=None, help_text='Date that this workspace was ready for RC uploaders to run compute.', null=True), - ), - migrations.AddField( - model_name='uploadcycle', - name='date_ready_for_compute', - field=models.DateField(blank=True, default=None, help_text='Date that this workspace was ready for RC uploaders to run compute.', null=True), - ), - ] diff --git a/gregor_django/gregor_anvil/migrations/0032_alter_historicaluploadworkspace_date_qc_completed_and_more.py b/gregor_django/gregor_anvil/migrations/0032_alter_historicaluploadworkspace_date_qc_completed_and_more.py deleted file mode 100644 index 17ef5e04..00000000 --- a/gregor_django/gregor_anvil/migrations/0032_alter_historicaluploadworkspace_date_qc_completed_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-22 21:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gregor_anvil', '0031_uploadcycle_change_is_ready_to_compute_to_date_ready_for_compute'), - ] - - operations = [ - migrations.AlterField( - model_name='historicaluploadworkspace', - name='date_qc_completed', - field=models.DateField(blank=True, default=None, help_text='Date that QC was completed for this workspace. If null, QC is not complete.', null=True), - ), - migrations.AlterField( - model_name='uploadworkspace', - name='date_qc_completed', - field=models.DateField(blank=True, default=None, help_text='Date that QC was completed for this workspace. If null, QC is not complete.', null=True), - ), - ] diff --git a/gregor_django/gregor_anvil/migrations/0033_alter_combinedconsortiumdataworkspace_date_completed_and_more.py b/gregor_django/gregor_anvil/migrations/0033_alter_combinedconsortiumdataworkspace_date_completed_and_more.py deleted file mode 100644 index ffb73a3e..00000000 --- a/gregor_django/gregor_anvil/migrations/0033_alter_combinedconsortiumdataworkspace_date_completed_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-22 21:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('gregor_anvil', '0032_alter_historicaluploadworkspace_date_qc_completed_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='combinedconsortiumdataworkspace', - name='date_completed', - field=models.DateField(blank=True, default=None, help_text='Date that data preparation in this workspace was completed.', null=True), - ), - migrations.AlterField( - model_name='historicalcombinedconsortiumdataworkspace', - name='date_completed', - field=models.DateField(blank=True, default=None, help_text='Date that data preparation in this workspace was completed.', null=True), - ), - ] From dcd3a42cbd82599dab53125bf929b01ec21a6da3 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 15:07:45 -0700 Subject: [PATCH 098/125] Add skeleton migrations and tests for populating new fields --- ...pulate_uploadcycle_is_ready_for_compute.py | 12 ++++++++ ...pulate_uploadworkspace_date_qc_complete.py | 12 ++++++++ ...tiumcombineddataworkspace_date_complete.py | 12 ++++++++ .../gregor_anvil/tests/test_migrations.py | 30 +++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_is_ready_for_compute.py create mode 100644 gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py create mode 100644 gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py diff --git a/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_is_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_is_ready_for_compute.py new file mode 100644 index 00000000..3e0818fa --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_is_ready_for_compute.py @@ -0,0 +1,12 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0027_tracking_fields_for_custom_audits"), + ] + + operations = [] diff --git a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py new file mode 100644 index 00000000..efbcb857 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py @@ -0,0 +1,12 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0028_populate_uploadcycle_is_ready_for_compute"), + ] + + operations = [] diff --git a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py new file mode 100644 index 00000000..f2fb37cd --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py @@ -0,0 +1,12 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_complete"), + ] + + operations = [] diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index 148617de..91061e6e 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -312,3 +312,33 @@ def test_relationships(self): self.assertTrue(hasattr(workspace, "exampleworkspace")) self.assertIsInstance(workspace.exampleworkspace, ExampleWorkspace) self.assertEqual(workspace.exampleworkspace, example_workspace) + + +class PopulateUploadCycleIsReadyForCompute(MigratorTestCase): + """Tests for the 0028_populate_uploadcycle_is_ready_for_compute migration.""" + + migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") + migrate_to = ("gregor_anvil", "0028_populate_uploadcycle_is_ready_for_compute") + + def prepare(self): + """Prepare some data before the migration.""" + + +class PopulateUploadWorkspaceDateQCComplete(MigratorTestCase): + """Tests for the 0029_populate_uploadworkspace_date_qc_complete migration.""" + + migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") + migrate_to = ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_complete") + + def prepare(self): + """Prepare some data before the migration.""" + + +class PopulateConsortiumCombinedDataWorkspaceIsComplete(MigratorTestCase): + """Tests for the 0030_populate_consortiumcombineddataworkspace_date_complete migration.""" + + migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") + migrate_to = ("gregor_anvil", "0030_populate_consortiumcombineddataworkspace_date_complete") + + def prepare(self): + """Prepare some data before the migration.""" From 9614d3e297044d754c905edd30dbba115d5f0046 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 15:27:43 -0700 Subject: [PATCH 099/125] Fill in migration to populate UploadCycle date_ready_for_compute --- ...late_uploadcycle_date_ready_for_compute.py | 29 +++++++++++++++ ...pulate_uploadcycle_is_ready_for_compute.py | 12 ------- ...pulate_uploadworkspace_date_qc_complete.py | 2 +- .../gregor_anvil/tests/test_migrations.py | 36 +++++++++++++++++-- 4 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py delete mode 100644 gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_is_ready_for_compute.py diff --git a/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py new file mode 100644 index 00000000..b9728c1b --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:04 + +from datetime import timedelta + +from django.db import migrations +from django.utils import timezone + + +def populate_uploadcycle_date_ready_for_compute(apps, schema_editor): + UploadCycle = apps.get_model("gregor_anvil", "UploadCycle") + # Create one UploadCycle for each unique version. + for row in UploadCycle.objects.all(): + assumed_date_ready_for_compute = row.start_date + timedelta(days=7) + if row.end_date <= timezone.localdate(): + row.date_ready_for_compute = assumed_date_ready_for_compute + row.full_clean() + row.save(update_fields=["date_ready_for_compute"]) + + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0027_tracking_fields_for_custom_audits"), + ] + + operations = [ + migrations.RunPython(populate_uploadcycle_date_ready_for_compute, reverse_code=migrations.RunPython.noop), + ] diff --git a/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_is_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_is_ready_for_compute.py deleted file mode 100644 index 3e0818fa..00000000 --- a/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_is_ready_for_compute.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-27 22:04 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("gregor_anvil", "0027_tracking_fields_for_custom_audits"), - ] - - operations = [] diff --git a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py index efbcb857..8b8fef26 100644 --- a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py +++ b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ("gregor_anvil", "0028_populate_uploadcycle_is_ready_for_compute"), + ("gregor_anvil", "0028_populate_uploadcycle_date_ready_for_compute"), ] operations = [] diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index 91061e6e..7d4b113d 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -1,9 +1,10 @@ """Tests for data migrations in the app.""" -from datetime import date +from datetime import date, timedelta import factory from anvil_consortium_manager.tests.factories import BillingProjectFactory, WorkspaceFactory +from django.utils import timezone from django_test_migrations.contrib.unittest_case import MigratorTestCase from . import factories @@ -314,14 +315,43 @@ def test_relationships(self): self.assertEqual(workspace.exampleworkspace, example_workspace) -class PopulateUploadCycleIsReadyForCompute(MigratorTestCase): +class PopulateUploadCycleIsReadyForComputeForwardMigrationTest(MigratorTestCase): """Tests for the 0028_populate_uploadcycle_is_ready_for_compute migration.""" migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") - migrate_to = ("gregor_anvil", "0028_populate_uploadcycle_is_ready_for_compute") + migrate_to = ("gregor_anvil", "0028_populate_uploadcycle_date_ready_for_compute") def prepare(self): """Prepare some data before the migration.""" + # Get model definition for the old state. + UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") + # Create a past upload cycle. + self.upload_cycle_past = UploadCycle.objects.create( + cycle=1, + start_date=timezone.localdate() - timedelta(days=30), + end_date=timezone.localdate() - timedelta(days=20), + ) + # Create a current upload cycle - nothing should change for this one. + self.upload_cycle_current = UploadCycle.objects.create( + cycle=2, + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=10), + ) + # Create a future upload cycle - nothing should change for this one. + self.upload_cycle_future = UploadCycle.objects.create( + cycle=3, + start_date=timezone.localdate() + timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=20), + ) + + def test_date_completed(self): + UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") + upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_past.pk) + self.assertEqual(upload_cycle.date_ready_for_compute, upload_cycle.start_date + timedelta(days=7)) + upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_current.pk) + self.assertIsNone(upload_cycle.date_ready_for_compute) + upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_future.pk) + self.assertIsNone(upload_cycle.date_ready_for_compute) class PopulateUploadWorkspaceDateQCComplete(MigratorTestCase): From 04de0dd444fdc2dcdf29d3fa445d894cb63e3fa8 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 15:59:26 -0700 Subject: [PATCH 100/125] Fill in migration to populate UploadWorkspace.date_qc_completed --- ...pulate_uploadworkspace_date_qc_complete.py | 12 ---- ...ulate_uploadworkspace_date_qc_completed.py | 27 +++++++ ...tiumcombineddataworkspace_date_complete.py | 2 +- .../gregor_anvil/tests/test_migrations.py | 72 ++++++++++++++++++- 4 files changed, 99 insertions(+), 14 deletions(-) delete mode 100644 gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py create mode 100644 gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py diff --git a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py deleted file mode 100644 index 8b8fef26..00000000 --- a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_complete.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-27 22:04 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("gregor_anvil", "0028_populate_uploadcycle_date_ready_for_compute"), - ] - - operations = [] diff --git a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py new file mode 100644 index 00000000..e860b22c --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:04 + +from datetime import timedelta + +from django.db import migrations +from django.utils import timezone + +def populate_uploadworkspace_date_qc_complete(apps, schema_editor): + UploadWorkspace = apps.get_model("gregor_anvil", "UploadWorkspace") + # Create one UploadWorkspace for each unique version. + for row in UploadWorkspace.objects.all(): + assumed_date_qc_completed = row.upload_cycle.end_date + timedelta(days=7) + if assumed_date_qc_completed <= timezone.localdate(): + row.date_qc_completed = assumed_date_qc_completed + row.full_clean() + row.save(update_fields=["date_qc_completed"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0028_populate_uploadcycle_date_ready_for_compute"), + ] + + operations = [ + migrations.RunPython(populate_uploadworkspace_date_qc_complete, reverse_code=migrations.RunPython.noop), + ] diff --git a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py index f2fb37cd..1fa40481 100644 --- a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py +++ b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_complete"), + ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_completed"), ] operations = [] diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index 7d4b113d..3d4606e4 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -358,10 +358,80 @@ class PopulateUploadWorkspaceDateQCComplete(MigratorTestCase): """Tests for the 0029_populate_uploadworkspace_date_qc_complete migration.""" migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") - migrate_to = ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_complete") + migrate_to = ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_completed") def prepare(self): """Prepare some data before the migration.""" + # Get model definition for the old state. + BillingProject = self.old_state.apps.get_model("anvil_consortium_manager", "BillingProject") + Workspace = self.old_state.apps.get_model("anvil_consortium_manager", "Workspace") + ResearchCenter = self.old_state.apps.get_model("gregor_anvil", "ResearchCenter") + ConsentGroup = self.old_state.apps.get_model("gregor_anvil", "ConsentGroup") + UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") + UploadWorkspace = self.old_state.apps.get_model("gregor_anvil", "UploadWorkspace") + # Make FKs. + consent_group = factory.create(ConsentGroup, FACTORY_CLASS=factories.ConsentGroupFactory) + research_center = ResearchCenter.objects.create(short_name="rc", full_name="Research Center") + # Create an upload workspace from a past upload cycle. + upload_cycle = UploadCycle.objects.create( + cycle=1, + start_date=timezone.localdate() - timedelta(days=30), + end_date=timezone.localdate() - timedelta(days=20), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + self.upload_workspace_past = UploadWorkspace.objects.create( + upload_cycle=upload_cycle, + research_center=research_center, + consent_group=consent_group, + workspace=workspace, + ) + # Create a current upload cycle - nothing should change for this one. + upload_cycle = UploadCycle.objects.create( + cycle=2, + start_date=timezone.localdate() - timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=10), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + self.upload_workspace_current = UploadWorkspace.objects.create( + upload_cycle=upload_cycle, + research_center=research_center, + consent_group=consent_group, + workspace=workspace, + ) + # Create a future upload cycle - nothing should change for this one. + upload_cycle = UploadCycle.objects.create( + cycle=3, + start_date=timezone.localdate() + timedelta(days=10), + end_date=timezone.localdate() + timedelta(days=20), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + self.upload_workspace_future = UploadWorkspace.objects.create( + upload_cycle=upload_cycle, + research_center=research_center, + consent_group=consent_group, + workspace=workspace, + ) + + def test_date_qc_completed(self): + UploadWorkspace = self.old_state.apps.get_model("gregor_anvil", "UploadWorkspace") + upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_past.pk) + self.assertEqual(upload_workspace.date_qc_completed, upload_workspace.upload_cycle.end_date + timedelta(days=7)) + upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_current.pk) + self.assertIsNone(upload_workspace.date_qc_completed) + upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_future.pk) + self.assertIsNone(upload_workspace.date_qc_completed) class PopulateConsortiumCombinedDataWorkspaceIsComplete(MigratorTestCase): From 28b995a98fe66bea9167cb96dca3e3b866610030 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 16:17:41 -0700 Subject: [PATCH 101/125] Fill in the migration to populate combined workspace date_completed --- ...tiumcombineddataworkspace_date_complete.py | 12 ---- ...iumcombineddataworkspace_date_completed.py | 34 ++++++++++ .../gregor_anvil/tests/test_migrations.py | 62 ++++++++++++++++++- 3 files changed, 95 insertions(+), 13 deletions(-) delete mode 100644 gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py create mode 100644 gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py diff --git a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py deleted file mode 100644 index 1fa40481..00000000 --- a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_complete.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-27 22:05 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_completed"), - ] - - operations = [] diff --git a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py new file mode 100644 index 00000000..eee7f366 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.15 on 2024-08-27 22:05 + +from datetime import timedelta + +from django.db import migrations +from django.utils import timezone + +def populate_combinedworkspace_date_completed(apps, schema_editor): + CombinedConsortiumDataWorkspace = apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") + WorkspaceGroupSharing = apps.get_model("anvil_consortium_manager", "WorkspaceGroupSharing") + # Create one UploadWorkspace for each unique version. + for row in CombinedConsortiumDataWorkspace.objects.all(): + try: + workspace_group_sharing = WorkspaceGroupSharing.objects.get( + workspace=row.workspace, + group__name="GREGOR_ALL", + ) + except WorkspaceGroupSharing.DoesNotExist: + continue + else: + row.date_completed = workspace_group_sharing.created.date() + row.full_clean() + row.save(update_fields=["date_completed"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("gregor_anvil", "0029_populate_uploadworkspace_date_qc_completed"), + ] + + operations = [ + migrations.RunPython(populate_combinedworkspace_date_completed, reverse_code=migrations.RunPython.noop), + ] diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index 3d4606e4..8b6834f0 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -6,6 +6,7 @@ from anvil_consortium_manager.tests.factories import BillingProjectFactory, WorkspaceFactory from django.utils import timezone from django_test_migrations.contrib.unittest_case import MigratorTestCase +from freezegun import freeze_time from . import factories @@ -438,7 +439,66 @@ class PopulateConsortiumCombinedDataWorkspaceIsComplete(MigratorTestCase): """Tests for the 0030_populate_consortiumcombineddataworkspace_date_complete migration.""" migrate_from = ("gregor_anvil", "0027_tracking_fields_for_custom_audits") - migrate_to = ("gregor_anvil", "0030_populate_consortiumcombineddataworkspace_date_complete") + migrate_to = ("gregor_anvil", "0030_populate_consortiumcombineddataworkspace_date_completed") def prepare(self): """Prepare some data before the migration.""" + # Get model definition for the old state. + BillingProject = self.old_state.apps.get_model("anvil_consortium_manager", "BillingProject") + Workspace = self.old_state.apps.get_model("anvil_consortium_manager", "Workspace") + ManagedGroup = self.old_state.apps.get_model("anvil_consortium_manager", "ManagedGroup") + WorkspaceGroupSharing = self.old_state.apps.get_model("anvil_consortium_manager", "WorkspaceGroupSharing") + UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") + CombinedConsortiumDataWorkspace = self.old_state.apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") + # Create fks - just the gregor all group here. + gregor_all_group = ManagedGroup.objects.create( + name="GREGOR_ALL", + email="GREGOR_ALL@firecloud.org", + is_managed_by_app=True, + ) + # Create a shared combined workspace. + upload_cycle = UploadCycle.objects.create( + cycle=1, + start_date=timezone.localdate() - timedelta(days=50), + end_date=timezone.localdate() - timedelta(days=40), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + self.combined_workspace_shared = CombinedConsortiumDataWorkspace.objects.create( + upload_cycle=upload_cycle, + workspace=workspace, + ) + self.date_shared = timezone.localdate() - timedelta(days=35) + with freeze_time(self.date_shared): + WorkspaceGroupSharing.objects.create( + workspace=workspace, + group=gregor_all_group, + access="READER", + can_compute=False, + ) + # Create a combined workspace that has not been shared. + upload_cycle = UploadCycle.objects.create( + cycle=2, + start_date=timezone.localdate() - timedelta(days=30), + end_date=timezone.localdate() - timedelta(days=20), + ) + workspace = factory.create( + Workspace, + FACTORY_CLASS=WorkspaceFactory, + billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), + ) + self.combined_workspace_not_shared = CombinedConsortiumDataWorkspace.objects.create( + upload_cycle=upload_cycle, + workspace=workspace, + ) + + + def test_date_completed(self): + CombinedConsortiumDataWorkspace = self.new_state.apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") + workspace = CombinedConsortiumDataWorkspace.objects.get(pk=self.combined_workspace_shared.pk) + self.assertEqual(workspace.date_completed, self.date_shared) + workspace = CombinedConsortiumDataWorkspace.objects.get(pk=self.combined_workspace_not_shared.pk) + self.assertIsNone(workspace.date_completed) From f9c3034586d8c8586e629557b5cce6254dbe3ce9 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 27 Aug 2024 16:36:15 -0700 Subject: [PATCH 102/125] Fix migration for combined workspace date_completed Instead of using the date for sharing with GREGOR_ALL, use the date when the workspace was shared with its auth domain. This is how we're actually sharing the workspace - GREGOR_ALL is part of the auth domain and the workspace is then shared with its auth domain. --- ...iumcombineddataworkspace_date_completed.py | 2 +- .../gregor_anvil/tests/test_migrations.py | 35 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py index eee7f366..a32852a4 100644 --- a/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py +++ b/gregor_django/gregor_anvil/migrations/0030_populate_consortiumcombineddataworkspace_date_completed.py @@ -13,7 +13,7 @@ def populate_combinedworkspace_date_completed(apps, schema_editor): try: workspace_group_sharing = WorkspaceGroupSharing.objects.get( workspace=row.workspace, - group__name="GREGOR_ALL", + group=row.workspace.authorization_domains.first(), ) except WorkspaceGroupSharing.DoesNotExist: continue diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index 8b6834f0..62dca0e9 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -3,7 +3,11 @@ from datetime import date, timedelta import factory -from anvil_consortium_manager.tests.factories import BillingProjectFactory, WorkspaceFactory +from anvil_consortium_manager.tests.factories import ( + BillingProjectFactory, + WorkspaceAuthorizationDomainFactory, + WorkspaceFactory, +) from django.utils import timezone from django_test_migrations.contrib.unittest_case import MigratorTestCase from freezegun import freeze_time @@ -444,17 +448,19 @@ class PopulateConsortiumCombinedDataWorkspaceIsComplete(MigratorTestCase): def prepare(self): """Prepare some data before the migration.""" # Get model definition for the old state. + ManagedGroup = self.old_state.apps.get_model("anvil_consortium_manager", "ManagedGroup") BillingProject = self.old_state.apps.get_model("anvil_consortium_manager", "BillingProject") Workspace = self.old_state.apps.get_model("anvil_consortium_manager", "Workspace") - ManagedGroup = self.old_state.apps.get_model("anvil_consortium_manager", "ManagedGroup") + WorkspaceAuthorizationDomain = self.old_state.apps.get_model( + "anvil_consortium_manager", "WorkspaceAuthorizationDomain" + ) WorkspaceGroupSharing = self.old_state.apps.get_model("anvil_consortium_manager", "WorkspaceGroupSharing") UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") CombinedConsortiumDataWorkspace = self.old_state.apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") - # Create fks - just the gregor all group here. - gregor_all_group = ManagedGroup.objects.create( - name="GREGOR_ALL", - email="GREGOR_ALL@firecloud.org", - is_managed_by_app=True, + # Create an auth domain for the combined workspaces. + auth_domain_group = ManagedGroup.objects.create( + name="auth_domain", + email="auth_domain@firecloud.org", ) # Create a shared combined workspace. upload_cycle = UploadCycle.objects.create( @@ -467,15 +473,22 @@ def prepare(self): FACTORY_CLASS=WorkspaceFactory, billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), ) + factory.create( + WorkspaceAuthorizationDomain, + FACTORY_CLASS=WorkspaceAuthorizationDomainFactory, + workspace=workspace, + group=auth_domain_group, + ) self.combined_workspace_shared = CombinedConsortiumDataWorkspace.objects.create( upload_cycle=upload_cycle, workspace=workspace, ) + # Shared with its auth domain. self.date_shared = timezone.localdate() - timedelta(days=35) with freeze_time(self.date_shared): WorkspaceGroupSharing.objects.create( workspace=workspace, - group=gregor_all_group, + group=auth_domain_group, access="READER", can_compute=False, ) @@ -490,6 +503,12 @@ def prepare(self): FACTORY_CLASS=WorkspaceFactory, billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), ) + factory.create( + WorkspaceAuthorizationDomain, + FACTORY_CLASS=WorkspaceAuthorizationDomainFactory, + workspace=workspace, + group=auth_domain_group, + ) self.combined_workspace_not_shared = CombinedConsortiumDataWorkspace.objects.create( upload_cycle=upload_cycle, workspace=workspace, From 06d535074d876288cdd9e1055fae800146bcc532 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 09:33:14 -0700 Subject: [PATCH 103/125] Show date_completed on combined workspace detail page --- .../gregor_anvil/combinedconsortiumdataworkspace_detail.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gregor_django/templates/gregor_anvil/combinedconsortiumdataworkspace_detail.html b/gregor_django/templates/gregor_anvil/combinedconsortiumdataworkspace_detail.html index bf43f32d..48832960 100644 --- a/gregor_django/templates/gregor_anvil/combinedconsortiumdataworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/combinedconsortiumdataworkspace_detail.html @@ -3,7 +3,8 @@ {% block workspace_data %}
    -
    Upload cycle
    {{ object.combinedconsortiumdataworkspace.upload_cycle }}
    +
    Upload cycle
    {{ workspace_data_object.upload_cycle }}
    +
    Date completed
    {{ workspace_data_object.date_completed }}
    {% endblock workspace_data %} From 4bc0d91cea7206ee4e789e8ae8530d8e56f22e96 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 11:29:04 -0700 Subject: [PATCH 104/125] Start a mangement command for auditing upload workspaces --- .../commands/run_upload_workspace_audit.py | 65 ++++ .../gregor_anvil/tests/test_commands.py | 300 ++++++++++++++++++ .../gregor_anvil/email_audit_report.html | 48 +++ 3 files changed, 413 insertions(+) create mode 100644 gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py create mode 100644 gregor_django/gregor_anvil/tests/test_commands.py create mode 100644 gregor_django/templates/gregor_anvil/email_audit_report.html diff --git a/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py b/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py new file mode 100644 index 00000000..4562cc74 --- /dev/null +++ b/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py @@ -0,0 +1,65 @@ +from django.contrib.sites.models import Site +from django.core.mail import send_mail +from django.core.management.base import BaseCommand +from django.template.loader import render_to_string + +from ...audit import upload_workspace_sharing_audit + + +class Command(BaseCommand): + help = "Run access audits on UploadWorkspace." + + def add_arguments(self, parser): + email_group = parser.add_argument_group(title="Email reports") + email_group.add_argument( + "--email", + help="""Email to which to send audit reports that need action or have errors.""", + ) + + def run_sharing_audit(self, *args, **options): + self.stdout.write("Running UploadWorkspace sharing audit... ", ending="") + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self._handle_audit_results(audit, "", **options) + + def _handle_audit_results(self, audit, url, **options): + # Report errors and needs access. + audit_ok = audit.ok() + # Construct the url for handling errors. + url = "https://" + Site.objects.get_current().domain + url + if audit_ok: + self.stdout.write(self.style.SUCCESS("ok!")) + else: + self.stdout.write(self.style.ERROR("problems found.")) + + # Print results + self.stdout.write("* Verified: {}".format(len(audit.verified))) + self.stdout.write("* Needs action: {}".format(len(audit.needs_action))) + self.stdout.write("* Errors: {}".format(len(audit.errors))) + + if not audit_ok: + self.stdout.write(self.style.ERROR(f"Please visit {url} to resolve these issues.")) + + # Send email if requested and there are problems. + email = options["email"] + subject = "{} - problems found".format(audit.__class__.__name__) + html_body = render_to_string( + "gregor_anvil/email_audit_report.html", + context={ + "title": "Upload workspace audit", + "audit_results": audit, + "url": url, + }, + ) + send_mail( + subject, + "Audit problems found. Please see attached report.", + None, + [email], + fail_silently=False, + html_message=html_body, + ) + + def handle(self, *args, **options): + self.run_sharing_audit(*args, **options) + # self.run_auth_domain_audit(*args, **options) diff --git a/gregor_django/gregor_anvil/tests/test_commands.py b/gregor_django/gregor_anvil/tests/test_commands.py new file mode 100644 index 00000000..6916a118 --- /dev/null +++ b/gregor_django/gregor_anvil/tests/test_commands.py @@ -0,0 +1,300 @@ +"""Tests for management commands in the `gregor_anvil` app.""" + +from io import StringIO + +from anvil_consortium_manager.models import WorkspaceGroupSharing +from anvil_consortium_manager.tests.factories import ( + GroupGroupMembershipFactory, + WorkspaceGroupSharingFactory, +) +from django.core import mail +from django.core.management import call_command +from django.test import TestCase + +from . import factories + + +class RunUploadWorkspaceAuditTest(TestCase): + """Tests for the run_upload_workspace_audit command""" + + def test_no_upload_workspaces(self): + """Test command output.""" + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... ok!", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... ok!", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_sharing_audit_one_instance_verified(self): + """Test command output with one verified instance.""" + # Create a workspace and matching DAR. + workspace = factories.UploadWorkspaceFactory.create() + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... ok!", + "* Verified: 1", + "* Needs action: 0", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_access_audit_one_instance_needs_action(self): + """Test command output with one needs_action instance.""" + # Create a workspace and matching DAR. + factories.UploadWorkspaceFactory.create() + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... problems found.", + "* Verified: 0", + "* Needs action: 1", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_access_audit_one_instance_error(self): + """Test command output with one error instance.""" + workspace = factories.UploadWorkspaceFactory.create() + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.OWNER, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... problems found.", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 1", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_access_audit_one_instance_verified_email(self): + """No email is sent when there are no errors.""" + # Create a workspace and matching DAR. + factories.dbGaPWorkspaceFactory.create() + factories.dbGaPApplicationFactory.create() + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + self.assertIn("Running UploadWorkspace sharing audit... ok!", out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_access_audit_one_instance_needs_action_email(self): + """Email is sent for one needs_action instance.""" + # Create a workspace and matching DAR. + dbgap_workspace = factories.dbGaPWorkspaceFactory.create() + factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... problems found.", + "* Verified: 0", + "* Needs action: 1", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # One message has been sent by default. + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "dbGaPAccessAudit - problems found") + + def test_access_audit_one_instance_error_email(self): + """Test command output with one error instance.""" + # Create a workspace and matching DAR. + dbgap_workspace = factories.dbGaPWorkspaceFactory.create() + dbgap_application = factories.dbGaPApplicationFactory.create() + GroupGroupMembershipFactory( + parent_group=dbgap_workspace.workspace.authorization_domains.first(), + child_group=dbgap_application.anvil_access_group, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace sharing audit... problems found.", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 1", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # One message has been sent by default. + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "dbGaPAccessAudit - problems found") + + # def test_access_audit_different_domain(self): + # """Test command output when a different domain is specified.""" + # site = Site.objects.create(domain="foobar.com", name="test") + # site.save() + # with self.settings(SITE_ID=site.id): + # dbgap_workspace = factories.dbGaPWorkspaceFactory.create() + # factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) + # out = StringIO() + # call_command("run_upload_workspace_audit", "--no-color", stdout=out) + # self.assertIn("Running UploadWorkspace sharing audit... problems found.", out.getvalue()) + # self.assertIn("https://foobar.com", out.getvalue()) + + # def test_collaborator_audit_one_instance_verified(self): + # """Test command output with one verified instance.""" + # # Create a workspace and matching DAR. + # factories.dbGaPApplicationFactory.create() + # # Verified no access for PI. + # out = StringIO() + # call_command("run_upload_workspace_audit", "--no-color", stdout=out) + # expected_string = "\n".join( + # [ + # "Running dbGaP collaborator audit... ok!", + # "* Verified: 1", + # "* Needs action: 0", + # "* Errors: 0", + # ] + # ) + # self.assertIn(expected_string, out.getvalue()) + # # Zero messages have been sent by default. + # self.assertEqual(len(mail.outbox), 0) + + # def test_collaborator_audit_one_instance_needs_action(self): + # """Test command output with one needs_action instance.""" + # application = factories.dbGaPApplicationFactory.create() + # AccountFactory.create(user=application.principal_investigator) + # out = StringIO() + # call_command("run_upload_workspace_audit", "--no-color", stdout=out) + # expected_string = "\n".join( + # [ + # "Running dbGaP collaborator audit... problems found.", + # "* Verified: 0", + # "* Needs action: 1", + # "* Errors: 0", + # ] + # ) + # self.assertIn(expected_string, out.getvalue()) + # # Zero messages have been sent by default. + # self.assertEqual(len(mail.outbox), 0) + + # def test_collaborator_audit_one_instance_error(self): + # """Test command output with one error instance.""" + # application = factories.dbGaPApplicationFactory.create() + # GroupGroupMembershipFactory( + # parent_group=application.anvil_access_group, + # ) + # out = StringIO() + # call_command("run_upload_workspace_audit", "--no-color", stdout=out) + # expected_string = "\n".join( + # [ + # "Running dbGaP collaborator audit... problems found.", + # "* Verified: 1", # PI - no linked account, verified no access. + # "* Needs action: 0", + # "* Errors: 1", + # ] + # ) + # self.assertIn(expected_string, out.getvalue()) + # # Zero messages have been sent by default. + # self.assertEqual(len(mail.outbox), 0) + + # def test_collaborator_audit_one_instance_verified_email(self): + # """No email is sent when there are no errors.""" + # factories.dbGaPApplicationFactory.create() + # # Verified no access for PI. + # out = StringIO() + # call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + # self.assertIn("Running dbGaP collaborator audit... ok!", out.getvalue()) + # # Zero messages have been sent by default. + # self.assertEqual(len(mail.outbox), 0) + + # def test_collaborator_audit_one_instance_needs_action_email(self): + # """Email is sent for one needs_action instance.""" + # # Create a workspace and matching DAR. + # application = factories.dbGaPApplicationFactory.create() + # AccountFactory.create(user=application.principal_investigator) + # out = StringIO() + # call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + # expected_string = "\n".join( + # [ + # "Running dbGaP collaborator audit... problems found.", + # "* Verified: 0", + # "* Needs action: 1", + # "* Errors: 0", + # ] + # ) + # self.assertIn(expected_string, out.getvalue()) + # # One message has been sent by default. + # self.assertEqual(len(mail.outbox), 1) + # email = mail.outbox[0] + # self.assertEqual(email.to, ["test@example.com"]) + # self.assertEqual(email.subject, "dbGaPCollaboratorAudit - problems found") + + # def test_collaborator_audit_one_instance_error_email(self): + # """Test command output with one error instance.""" + # application = factories.dbGaPApplicationFactory.create() + # GroupGroupMembershipFactory( + # parent_group=application.anvil_access_group, + # ) + # out = StringIO() + # call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + # expected_string = "\n".join( + # [ + # "Running dbGaP collaborator audit... problems found.", + # "* Verified: 1", # PI - no linked account, verified no access. + # "* Needs action: 0", + # "* Errors: 1", + # ] + # ) + # self.assertIn(expected_string, out.getvalue()) + # # One message has been sent by default. + # self.assertEqual(len(mail.outbox), 1) + # email = mail.outbox[0] + # self.assertEqual(email.to, ["test@example.com"]) + # self.assertEqual(email.subject, "dbGaPCollaboratorAudit - problems found") + + # def test_collaborator_audit_different_domain(self): + # """Test command output when a different domain is specified.""" + # site = Site.objects.create(domain="foobar.com", name="test") + # site.save() + # with self.settings(SITE_ID=site.id): + # application = factories.dbGaPApplicationFactory.create() + # AccountFactory.create(user=application.principal_investigator) + # out = StringIO() + # call_command("run_upload_workspace_audit", "--no-color", stdout=out) + # self.assertIn("Running dbGaP collaborator audit... problems found.", out.getvalue()) + # self.assertIn("https://foobar.com", out.getvalue()) diff --git a/gregor_django/templates/gregor_anvil/email_audit_report.html b/gregor_django/templates/gregor_anvil/email_audit_report.html new file mode 100644 index 00000000..fb1f74c4 --- /dev/null +++ b/gregor_django/templates/gregor_anvil/email_audit_report.html @@ -0,0 +1,48 @@ + +{% load static i18n %} +{% load render_table from django_tables2 %} +{% get_current_language as LANGUAGE_CODE %} + + + Audit report + + + + +
    + +{% block content %} + +

    {{ title }}

    + +

    Please visit {{url}} to resolve.

    + +

    Verified

    +
    + {{ audit_results.verified|length }} record(s) verified. +
    + +

    Needs action - {{audit_results.needs_action|length }} record(s)

    +
    +
      + {% for record in audit_results.needs_action %} +
    • {{ record|stringformat:'r' }}
    • + {% endfor %} +
    +
    + +

    Errors - {{audit_results.errors|length }} record(s)

    +
    +
      + {% for record in data_access_audit.errors %} +
    • {{ record|stringformat:'r' }}
    • + {% endfor %} +
    +
    + + +{% endblock content %} + +
    + + From 25278f4b60849c9a012a5f9f29862695f60cefa7 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 11:55:21 -0700 Subject: [PATCH 105/125] Change audit results to errors when shared as owner For groups that should only ever have read/write permission, change the audit results to errors when the group has OWNER permission. This is more sensible than having a "needs action" result, since this case should never occur. --- .../audit/upload_workspace_sharing_audit.py | 126 +++++++----------- .../gregor_anvil/tests/test_audit.py | 108 +++++++-------- 2 files changed, 99 insertions(+), 135 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py index e17939e6..54c5fe13 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py @@ -286,112 +286,64 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group if upload_cycle.is_future: note = self.RC_UPLOADERS_FUTURE_CYCLE - if ( + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareAsWriter(note=note, **audit_result_args)) + elif ( current_sharing and current_sharing.access == WorkspaceGroupSharing.WRITER and not current_sharing.can_compute ): - self.verified.append( - VerifiedShared( - note=note, - **audit_result_args, - ) - ) + self.verified.append(VerifiedShared(note=note, **audit_result_args)) else: - self.needs_action.append( - ShareAsWriter( - note=note, - **audit_result_args, - ) - ) + self.needs_action.append(ShareAsWriter(note=note, **audit_result_args)) elif upload_cycle.is_current and not upload_cycle.date_ready_for_compute: note = self.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE - if ( + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareAsWriter(note=note, **audit_result_args)) + elif ( current_sharing and current_sharing.access == WorkspaceGroupSharing.WRITER and not current_sharing.can_compute ): - self.verified.append( - VerifiedShared( - note=note, - **audit_result_args, - ) - ) + self.verified.append(VerifiedShared(note=note, **audit_result_args)) else: - self.needs_action.append( - ShareAsWriter( - note=note, - **audit_result_args, - ) - ) + self.needs_action.append(ShareAsWriter(note=note, **audit_result_args)) elif upload_cycle.is_current and upload_cycle.date_ready_for_compute: note = self.RC_UPLOADERS_CURRENT_CYCLE_AFTER_COMPUTE - if ( + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareWithCompute(note=note, **audit_result_args)) + elif ( current_sharing and current_sharing.access == WorkspaceGroupSharing.WRITER and current_sharing.can_compute ): - self.verified.append( - VerifiedShared( - note=note, - **audit_result_args, - ) - ) + self.verified.append(VerifiedShared(note=note, **audit_result_args)) else: - self.needs_action.append( - ShareWithCompute( - note=note, - **audit_result_args, - ) - ) + self.needs_action.append(ShareWithCompute(note=note, **audit_result_args)) elif upload_cycle.is_past and not upload_workspace.date_qc_completed: note = self.RC_UPLOADERS_PAST_CYCLE_BEFORE_QC_COMPLETE if not current_sharing: - self.verified.append( - VerifiedNotShared( - note=note, - **audit_result_args, - ) - ) + self.verified.append(VerifiedNotShared(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) else: - self.needs_action.append( - StopSharing( - note=note, - **audit_result_args, - ) - ) + self.needs_action.append(StopSharing(note=note, **audit_result_args)) elif upload_cycle.is_past and upload_workspace.date_qc_completed and not combined_workspace: note = self.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE if not current_sharing: - self.verified.append( - VerifiedNotShared( - note=note, - **audit_result_args, - ) - ) + self.verified.append(VerifiedNotShared(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) else: - self.needs_action.append( - StopSharing( - note=note, - **audit_result_args, - ) - ) + self.needs_action.append(StopSharing(note=note, **audit_result_args)) elif upload_cycle.is_past and combined_workspace: note = self.RC_UPLOADERS_PAST_CYCLE_COMBINED_WORKSPACE_READY if not current_sharing: - self.verified.append( - VerifiedNotShared( - note=note, - **audit_result_args, - ) - ) + self.verified.append(VerifiedNotShared(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) else: - self.needs_action.append( - StopSharing( - note=note, - **audit_result_args, - ) - ) + self.needs_action.append(StopSharing(note=note, **audit_result_args)) else: raise ValueError("No case matched for RC uploader group.") @@ -415,7 +367,9 @@ def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group) if upload_cycle.is_future: note = self.DCC_WRITERS_FUTURE_CYCLE - if ( + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareWithCompute(note=note, **audit_result_args)) + elif ( current_sharing and current_sharing.access == WorkspaceGroupSharing.WRITER and current_sharing.can_compute @@ -435,7 +389,9 @@ def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group) ) elif upload_cycle.is_current: note = self.DCC_WRITERS_CURRENT_CYCLE - if ( + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareWithCompute(note=note, **audit_result_args)) + elif ( current_sharing and current_sharing.access == WorkspaceGroupSharing.WRITER and current_sharing.can_compute @@ -455,7 +411,9 @@ def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group) ) elif upload_cycle.is_past and not upload_workspace.date_qc_completed: note = self.DCC_WRITERS_PAST_CYCLE_BEFORE_QC_COMPLETE - if ( + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareWithCompute(note=note, **audit_result_args)) + elif ( current_sharing and current_sharing.access == WorkspaceGroupSharing.WRITER and current_sharing.can_compute @@ -475,7 +433,9 @@ def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group) ) elif upload_cycle.is_past and upload_workspace.date_qc_completed and not combined_workspace: note = self.DCC_WRITERS_PAST_CYCLE_AFTER_QC_COMPLETE - if not current_sharing: + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) + elif not current_sharing: self.verified.append( VerifiedNotShared( note=note, @@ -491,7 +451,9 @@ def _audit_workspace_and_dcc_writer_group(self, upload_workspace, managed_group) ) elif upload_cycle.is_past and combined_workspace: note = self.DCC_WRITERS_PAST_CYCLE_COMBINED_WORKSPACE_READY - if not current_sharing: + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) + elif not current_sharing: self.verified.append( VerifiedNotShared( note=note, @@ -524,7 +486,9 @@ def _audit_workspace_and_auth_domain(self, upload_workspace, managed_group): } note = self.AUTH_DOMAIN_AS_READER - if current_sharing and current_sharing.access == WorkspaceGroupSharing.READER: + if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(ShareAsReader(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.READER: self.verified.append( VerifiedShared( note=note, diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 479b4b86..f77b4573 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -425,9 +425,9 @@ def test_uploaders_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) @@ -516,9 +516,9 @@ def test_dcc_writers_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.dcc_writer_group) @@ -599,9 +599,9 @@ def test_auth_domain_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.auth_domain) @@ -999,9 +999,9 @@ def test_uploaders_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) @@ -1091,9 +1091,9 @@ def test_dcc_writers_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.dcc_writer_group) @@ -1174,9 +1174,9 @@ def test_auth_domain_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.auth_domain) @@ -1576,9 +1576,9 @@ def test_uploaders_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) @@ -1668,9 +1668,9 @@ def test_dcc_writers_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.dcc_writer_group) @@ -1751,9 +1751,9 @@ def test_auth_domain_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.auth_domain) @@ -2150,9 +2150,9 @@ def test_uploaders_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) @@ -2246,9 +2246,9 @@ def test_dcc_writers_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareWithCompute) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.dcc_writer_group) @@ -2330,9 +2330,9 @@ def test_auth_domain_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.auth_domain) @@ -2730,9 +2730,9 @@ def test_uploaders_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) @@ -2826,9 +2826,9 @@ def test_dcc_writers_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.dcc_writer_group) @@ -2910,9 +2910,9 @@ def test_auth_domain_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.auth_domain) @@ -3318,9 +3318,9 @@ def test_uploaders_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) @@ -3414,9 +3414,9 @@ def test_dcc_writers_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.dcc_writer_group) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.dcc_writer_group) @@ -3498,9 +3498,9 @@ def test_auth_domain_shared_as_owner(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.auth_domain) self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) - self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.auth_domain) From 0c97a675dc8f14c8842deae6aa3a76679d7672bd Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 12:11:40 -0700 Subject: [PATCH 106/125] Continue adding tests for audit management command --- .../gregor_anvil/tests/test_commands.py | 69 +++++++++++-------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_commands.py b/gregor_django/gregor_anvil/tests/test_commands.py index 6916a118..e489058f 100644 --- a/gregor_django/gregor_anvil/tests/test_commands.py +++ b/gregor_django/gregor_anvil/tests/test_commands.py @@ -4,12 +4,13 @@ from anvil_consortium_manager.models import WorkspaceGroupSharing from anvil_consortium_manager.tests.factories import ( - GroupGroupMembershipFactory, WorkspaceGroupSharingFactory, ) +from django.contrib.sites.models import Site from django.core import mail from django.core.management import call_command from django.test import TestCase +from django.urls import reverse from . import factories @@ -64,7 +65,7 @@ def test_sharing_audit_one_instance_verified(self): # Zero messages have been sent by default. self.assertEqual(len(mail.outbox), 0) - def test_access_audit_one_instance_needs_action(self): + def test_sharing_audit_one_instance_needs_action(self): """Test command output with one needs_action instance.""" # Create a workspace and matching DAR. factories.UploadWorkspaceFactory.create() @@ -82,7 +83,7 @@ def test_access_audit_one_instance_needs_action(self): # Zero messages have been sent by default. self.assertEqual(len(mail.outbox), 0) - def test_access_audit_one_instance_error(self): + def test_sharing_audit_one_instance_error(self): """Test command output with one error instance.""" workspace = factories.UploadWorkspaceFactory.create() WorkspaceGroupSharingFactory.create( @@ -104,22 +105,22 @@ def test_access_audit_one_instance_error(self): # Zero messages have been sent by default. self.assertEqual(len(mail.outbox), 0) - def test_access_audit_one_instance_verified_email(self): + def test_sharing_audit_one_instance_verified_email(self): """No email is sent when there are no errors.""" - # Create a workspace and matching DAR. - factories.dbGaPWorkspaceFactory.create() - factories.dbGaPApplicationFactory.create() + workspace = factories.UploadWorkspaceFactory.create() + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + ) out = StringIO() call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) self.assertIn("Running UploadWorkspace sharing audit... ok!", out.getvalue()) # Zero messages have been sent by default. self.assertEqual(len(mail.outbox), 0) - def test_access_audit_one_instance_needs_action_email(self): + def test_sharing_audit_one_instance_needs_action_email(self): """Email is sent for one needs_action instance.""" - # Create a workspace and matching DAR. - dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) + factories.UploadWorkspaceFactory.create() out = StringIO() call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) expected_string = "\n".join( @@ -135,16 +136,16 @@ def test_access_audit_one_instance_needs_action_email(self): self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertEqual(email.to, ["test@example.com"]) - self.assertEqual(email.subject, "dbGaPAccessAudit - problems found") + self.assertEqual(email.subject, "UploadWorkspaceSharingAudit - problems found") - def test_access_audit_one_instance_error_email(self): + def test_sharing_audit_one_instance_error_email(self): """Test command output with one error instance.""" # Create a workspace and matching DAR. - dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - dbgap_application = factories.dbGaPApplicationFactory.create() - GroupGroupMembershipFactory( - parent_group=dbgap_workspace.workspace.authorization_domains.first(), - child_group=dbgap_application.anvil_access_group, + workspace = factories.UploadWorkspaceFactory.create() + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, + group=workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.OWNER, ) out = StringIO() call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) @@ -161,19 +162,27 @@ def test_access_audit_one_instance_error_email(self): self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertEqual(email.to, ["test@example.com"]) - self.assertEqual(email.subject, "dbGaPAccessAudit - problems found") + self.assertEqual(email.subject, "UploadWorkspaceSharingAudit - problems found") - # def test_access_audit_different_domain(self): - # """Test command output when a different domain is specified.""" - # site = Site.objects.create(domain="foobar.com", name="test") - # site.save() - # with self.settings(SITE_ID=site.id): - # dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - # factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) - # out = StringIO() - # call_command("run_upload_workspace_audit", "--no-color", stdout=out) - # self.assertIn("Running UploadWorkspace sharing audit... problems found.", out.getvalue()) - # self.assertIn("https://foobar.com", out.getvalue()) + def test_sharing_audit_one_instance_needs_action_link_in_output(self): + factories.UploadWorkspaceFactory.create() + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + url = reverse("gregor_anvil:audit:upload_workspaces:sharing:all") + self.assertIn(url, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_sharing_audit_different_domain(self): + """Test command output when a different domain is specified.""" + site = Site.objects.create(domain="foobar.com", name="test") + site.save() + with self.settings(SITE_ID=site.id): + factories.UploadWorkspaceFactory.create() + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + self.assertIn("Running UploadWorkspace sharing audit... problems found.", out.getvalue()) + self.assertIn("https://foobar.com", out.getvalue()) # def test_collaborator_audit_one_instance_verified(self): # """Test command output with one verified instance.""" From 13f16440fd2c92cd8ba5eef4bf1928692a088fc7 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 12:46:54 -0700 Subject: [PATCH 107/125] Also run the UploadWorkspaceAuthDomain audit with management command --- .../commands/run_upload_workspace_audit.py | 10 +- .../gregor_anvil/tests/test_commands.py | 293 +++++++++++------- 2 files changed, 183 insertions(+), 120 deletions(-) diff --git a/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py b/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py index 4562cc74..6cd5a75e 100644 --- a/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py @@ -3,7 +3,7 @@ from django.core.management.base import BaseCommand from django.template.loader import render_to_string -from ...audit import upload_workspace_sharing_audit +from ...audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit class Command(BaseCommand): @@ -22,6 +22,12 @@ def run_sharing_audit(self, *args, **options): audit.run_audit() self._handle_audit_results(audit, "", **options) + def run_auth_domain_audit(self, *args, **options): + self.stdout.write("Running UploadWorkspace auth domain audit... ", ending="") + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self._handle_audit_results(audit, "", **options) + def _handle_audit_results(self, audit, url, **options): # Report errors and needs access. audit_ok = audit.ok() @@ -62,4 +68,4 @@ def _handle_audit_results(self, audit, url, **options): def handle(self, *args, **options): self.run_sharing_audit(*args, **options) - # self.run_auth_domain_audit(*args, **options) + self.run_auth_domain_audit(*args, **options) diff --git a/gregor_django/gregor_anvil/tests/test_commands.py b/gregor_django/gregor_anvil/tests/test_commands.py index e489058f..017dae7f 100644 --- a/gregor_django/gregor_anvil/tests/test_commands.py +++ b/gregor_django/gregor_anvil/tests/test_commands.py @@ -2,10 +2,13 @@ from io import StringIO -from anvil_consortium_manager.models import WorkspaceGroupSharing +from anvil_consortium_manager.models import GroupGroupMembership, WorkspaceGroupSharing from anvil_consortium_manager.tests.factories import ( + GroupGroupMembershipFactory, + ManagedGroupFactory, WorkspaceGroupSharingFactory, ) +from django.conf import settings from django.contrib.sites.models import Site from django.core import mail from django.core.management import call_command @@ -184,126 +187,180 @@ def test_sharing_audit_different_domain(self): self.assertIn("Running UploadWorkspace sharing audit... problems found.", out.getvalue()) self.assertIn("https://foobar.com", out.getvalue()) - # def test_collaborator_audit_one_instance_verified(self): - # """Test command output with one verified instance.""" - # # Create a workspace and matching DAR. - # factories.dbGaPApplicationFactory.create() - # # Verified no access for PI. - # out = StringIO() - # call_command("run_upload_workspace_audit", "--no-color", stdout=out) - # expected_string = "\n".join( - # [ - # "Running dbGaP collaborator audit... ok!", - # "* Verified: 1", - # "* Needs action: 0", - # "* Errors: 0", - # ] - # ) - # self.assertIn(expected_string, out.getvalue()) - # # Zero messages have been sent by default. - # self.assertEqual(len(mail.outbox), 0) + def test_auth_domain_audit_one_instance_verified(self): + """Test command output with one verified instance.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group__name=settings.ANVIL_DCC_ADMINS_GROUP_NAME, + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... ok!", + "* Verified: 1", + "* Needs action: 0", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) + + def test_auth_domain_audit_one_instance_needs_action(self): + """Test command output with one needs_action instance.""" + factories.UploadWorkspaceFactory.create() + ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... problems found.", + "* Verified: 0", + "* Needs action: 1", + "* Errors: 0", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) - # def test_collaborator_audit_one_instance_needs_action(self): - # """Test command output with one needs_action instance.""" - # application = factories.dbGaPApplicationFactory.create() - # AccountFactory.create(user=application.principal_investigator) - # out = StringIO() - # call_command("run_upload_workspace_audit", "--no-color", stdout=out) - # expected_string = "\n".join( - # [ - # "Running dbGaP collaborator audit... problems found.", - # "* Verified: 0", - # "* Needs action: 1", - # "* Errors: 0", - # ] - # ) - # self.assertIn(expected_string, out.getvalue()) - # # Zero messages have been sent by default. - # self.assertEqual(len(mail.outbox), 0) + def test_auth_domain_audit_one_instance_error(self): + """Test command output with one error instance.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + # Share with the auth domain to prevent an error in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... problems found.", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 1", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) - # def test_collaborator_audit_one_instance_error(self): - # """Test command output with one error instance.""" - # application = factories.dbGaPApplicationFactory.create() - # GroupGroupMembershipFactory( - # parent_group=application.anvil_access_group, - # ) - # out = StringIO() - # call_command("run_upload_workspace_audit", "--no-color", stdout=out) - # expected_string = "\n".join( - # [ - # "Running dbGaP collaborator audit... problems found.", - # "* Verified: 1", # PI - no linked account, verified no access. - # "* Needs action: 0", - # "* Errors: 1", - # ] - # ) - # self.assertIn(expected_string, out.getvalue()) - # # Zero messages have been sent by default. - # self.assertEqual(len(mail.outbox), 0) + def test_auth_domain_audit_one_instance_verified_email(self): + """No email is sent when there are no errors.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + dcc_admins_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + ) + # Share with the DCC admin group to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=dcc_admins_group, + access=WorkspaceGroupSharing.OWNER, + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=dcc_admins_group, + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + self.assertIn("Running UploadWorkspace auth domain audit... ok!", out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) - # def test_collaborator_audit_one_instance_verified_email(self): - # """No email is sent when there are no errors.""" - # factories.dbGaPApplicationFactory.create() - # # Verified no access for PI. - # out = StringIO() - # call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) - # self.assertIn("Running dbGaP collaborator audit... ok!", out.getvalue()) - # # Zero messages have been sent by default. - # self.assertEqual(len(mail.outbox), 0) + def test_auth_domain_audit_one_instance_needs_action_email(self): + """Email is sent for one needs_action instance.""" + # Create a workspace and matching DAR. + upload_workspace = factories.UploadWorkspaceFactory.create() + dcc_admins_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + ) + # Share with the DCC admin group to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=dcc_admins_group, + access=WorkspaceGroupSharing.OWNER, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + # One message has been sent by default. + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "UploadWorkspaceAuthDomainAudit - problems found") - # def test_collaborator_audit_one_instance_needs_action_email(self): - # """Email is sent for one needs_action instance.""" - # # Create a workspace and matching DAR. - # application = factories.dbGaPApplicationFactory.create() - # AccountFactory.create(user=application.principal_investigator) - # out = StringIO() - # call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) - # expected_string = "\n".join( - # [ - # "Running dbGaP collaborator audit... problems found.", - # "* Verified: 0", - # "* Needs action: 1", - # "* Errors: 0", - # ] - # ) - # self.assertIn(expected_string, out.getvalue()) - # # One message has been sent by default. - # self.assertEqual(len(mail.outbox), 1) - # email = mail.outbox[0] - # self.assertEqual(email.to, ["test@example.com"]) - # self.assertEqual(email.subject, "dbGaPCollaboratorAudit - problems found") + def test_auth_domain_audit_one_instance_error_email(self): + """Test command output with one error instance.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + # Share with the auth domain to prevent a problem in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=WorkspaceGroupSharing.READER, + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) + expected_string = "\n".join( + [ + "Running UploadWorkspace auth domain audit... problems found.", + "* Verified: 0", + "* Needs action: 0", + "* Errors: 1", + ] + ) + self.assertIn(expected_string, out.getvalue()) + # One message has been sent by default. + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "UploadWorkspaceAuthDomainAudit - problems found") - # def test_collaborator_audit_one_instance_error_email(self): - # """Test command output with one error instance.""" - # application = factories.dbGaPApplicationFactory.create() - # GroupGroupMembershipFactory( - # parent_group=application.anvil_access_group, - # ) - # out = StringIO() - # call_command("run_upload_workspace_audit", "--no-color", email="test@example.com", stdout=out) - # expected_string = "\n".join( - # [ - # "Running dbGaP collaborator audit... problems found.", - # "* Verified: 1", # PI - no linked account, verified no access. - # "* Needs action: 0", - # "* Errors: 1", - # ] - # ) - # self.assertIn(expected_string, out.getvalue()) - # # One message has been sent by default. - # self.assertEqual(len(mail.outbox), 1) - # email = mail.outbox[0] - # self.assertEqual(email.to, ["test@example.com"]) - # self.assertEqual(email.subject, "dbGaPCollaboratorAudit - problems found") + def test_auth_domain_audit_different_domain(self): + """Test command output when a different domain is specified.""" + site = Site.objects.create(domain="foobar.com", name="test") + site.save() + with self.settings(SITE_ID=site.id): + factories.UploadWorkspaceFactory.create() + ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + self.assertIn("Running UploadWorkspace auth domain audit... problems found.", out.getvalue()) + self.assertIn("https://foobar.com", out.getvalue()) - # def test_collaborator_audit_different_domain(self): - # """Test command output when a different domain is specified.""" - # site = Site.objects.create(domain="foobar.com", name="test") - # site.save() - # with self.settings(SITE_ID=site.id): - # application = factories.dbGaPApplicationFactory.create() - # AccountFactory.create(user=application.principal_investigator) - # out = StringIO() - # call_command("run_upload_workspace_audit", "--no-color", stdout=out) - # self.assertIn("Running dbGaP collaborator audit... problems found.", out.getvalue()) - # self.assertIn("https://foobar.com", out.getvalue()) + def test_auth_domain_audit_one_instance_needs_action_link_in_output(self): + upload_workspace = factories.UploadWorkspaceFactory.create() + # Share with the auth domain to prevent an error in the sharing audit. + WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + ) + GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + role=GroupGroupMembership.ADMIN, + ) + out = StringIO() + call_command("run_upload_workspace_audit", "--no-color", stdout=out) + url = reverse("gregor_anvil:audit:upload_workspaces:auth_domain:all") + self.assertIn(url, out.getvalue()) + # Zero messages have been sent by default. + self.assertEqual(len(mail.outbox), 0) From 97597791e11d8cce7e07b4a18c07f96849ba18aa Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 13:00:15 -0700 Subject: [PATCH 108/125] Add view to show all sharing audit results --- .../gregor_anvil/tests/test_views.py | 307 ++++++++++++++++++ gregor_django/gregor_anvil/urls.py | 2 +- gregor_django/gregor_anvil/views.py | 17 + 3 files changed, 325 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 0b3bbe38..59a0265f 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -2364,6 +2364,313 @@ def test_cc_admins_membership(self): self.assertEqual(membership.role, acm_models.GroupGroupMembership.ADMIN) +class UploadWorkspaceSharingAuditTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceSharingAudit view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:sharing:all", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceSharingAudit.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url()) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url()) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_context_audit_results_no_upload_workspaces(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + + def test_context_audit_results_one_upload_workspace(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(upload_workspace, audit_results.queryset) + + def test_context_audit_results_two_upload_workspaces(self): + """The audit_results exists in the context.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 2) + self.assertIn(upload_workspace_1, audit_results.queryset) + self.assertIn(upload_workspace_2, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, group=group, access=acm_models.WorkspaceGroupSharing.OWNER + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), acm_models.WorkspaceGroupSharing.OWNER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_reader(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = upload_workspace.workspace.authorization_domains.first() + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_writer(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_future=True, + research_center__uploader_group=group, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_with_compute(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_WRITERS_FUTURE_CYCLE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_share_as_owner(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("access")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.DCC_ADMIN_AS_OWNER, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_stop_sharing(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + # Change upload workspace end dates so it's in the past. + group = acm_factories.ManagedGroupFactory.create() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_past=True, + research_center__uploader_group=group, + date_qc_completed=timezone.localdate() - timedelta(days=1), + ) + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_PAST_CYCLE_AFTER_QC_COMPLETE, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_stop_sharing(self): + """error_table shows a record when an audit error is detected.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + # Create a sharing record. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=group, + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Share with the auth domain to prevent that audit error. + acm_factories.WorkspaceGroupSharingFactory.create( + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), + access=acm_models.WorkspaceGroupSharing.READER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("access"), "READER") + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.OTHER_GROUP_NO_ACCESS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + class UploadWorkspaceSharingAuditByWorkspaceTest(AnVILAPIMockTestMixin, TestCase): """Tests for the UploadWorkspaceSharingAuditByWorkspace view.""" diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 59430294..13aa2661 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -48,7 +48,7 @@ upload_workspace_sharing_audit_patterns = ( [ - # path("all/", views.UploadWorkspaceSharingAuditAll.as_view(), name="all"), + path("all/", views.UploadWorkspaceSharingAudit.as_view(), name="all"), path( "resolve////", views.UploadWorkspaceSharingAuditResolve.as_view(), diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 108b767b..31e5a20a 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -188,6 +188,23 @@ def get_context_data(self, **kwargs): return context +class UploadWorkspaceSharingAudit(AnVILConsortiumManagerStaffViewRequired, TemplateView): + """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + + template_name = "gregor_anvil/upload_workspace_sharing_audit.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + class UploadWorkspaceSharingAuditByWorkspace(AnVILConsortiumManagerStaffViewRequired, DetailView): """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" From 8189c40b0f86b048abf32992f84edaa2243550eb Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 14:41:16 -0700 Subject: [PATCH 109/125] Add a view to audit all upload auth domains at the same time --- .../gregor_anvil/tests/test_views.py | 319 ++++++++++++++++++ gregor_django/gregor_anvil/urls.py | 1 + gregor_django/gregor_anvil/views.py | 19 +- 3 files changed, 338 insertions(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 59a0265f..5d063d23 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -4504,6 +4504,325 @@ def test_post_new_share_as_writer_group_not_found_on_anvil_htmx(self): self.assertEqual(len(messages), 0) +class UploadWorkspaceAuthDomainAuditTest(AnVILAPIMockTestMixin, TestCase): + """Tests for the UploadWorkspaceSharingAudit view.""" + + def setUp(self): + """Set up test class.""" + super().setUp() + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:all", + args=args, + ) + + def get_view(self): + """Return the view being tested.""" + return views.UploadWorkspaceAuthDomainAuditByUploadCycle.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url()) + self.assertRedirects( + response, + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(), + ) + + def test_status_code_with_user_permission_view(self): + """Returns successful response code if the user has view permission.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertEqual(response.status_code, 200) + + def test_access_without_user_permission(self): + """Raises permission denied if user has no permissions.""" + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url()) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_invalid_upload_cycle(self): + """Raises a 404 error with an invalid upload cycle.""" + request = self.factory.get(self.get_url()) + request.user = self.user + with self.assertRaises(Http404): + self.get_view()(request) + + def test_context_audit_results_no_upload_workspaces(self): + """The audit_results exists in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 0) + + def test_context_audit_results_one_upload_workspace(self): + """The audit_results exists in the context.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 1) + self.assertIn(upload_workspace, audit_results.queryset) + + def test_context_audit_results_two_upload_workspaces(self): + """The audit_results exists in the context.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("audit_results", response.context_data) + audit_results = response.context_data["audit_results"] + self.assertIsInstance( + audit_results, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit, + ) + self.assertTrue(audit_results.completed) + self.assertEqual(audit_results.queryset.count(), 2) + self.assertIn(upload_workspace_1, audit_results.queryset) + self.assertIn(upload_workspace_2, audit_results.queryset) + + def test_context_verified_table_access(self): + """verified_table shows a record when audit has verified access.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_verified_table_no_access(self): + """verified_table shows a record when audit has verifiednotmember.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_past=True) + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("verified_table", response.context_data) + table = response.context_data["verified_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), None) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_member(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_future=True) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_add_admin(self): + """needs_action_table shows a record when audit finds that access needs to be granted.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertIsNone(table.rows[0].get_cell_value("role")) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_remove(self): + """needs_action_table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create(upload_cycle__is_past=True) + # Create and share a combined workspace. + factories.CombinedConsortiumDataWorkspaceFactory( + upload_cycle=upload_workspace.upload_cycle, + date_completed=timezone.localdate() - timedelta(days=1), + ) + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_AFTER_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_error_table_remove(self): + """error table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create() + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.OTHER_GROUP, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_errors_table_change_to_member(self): + """errors table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name="GREGOR_DCC_WRITERS") + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.ADMIN, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("errors_table", response.context_data) + table = response.context_data["errors_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.ADMIN) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_BEFORE_COMBINED, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + def test_context_needs_action_table_change_to_admin(self): + """error table shows a record when audit finds that access needs to be removed.""" + upload_workspace = factories.UploadWorkspaceFactory.create() + group = acm_factories.ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + acm_factories.GroupGroupMembershipFactory.create( + parent_group=upload_workspace.workspace.authorization_domains.first(), + child_group=group, + role=acm_models.GroupGroupMembership.MEMBER, + ) + # Check the table in the context. + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("needs_action_table", response.context_data) + table = response.context_data["needs_action_table"] + self.assertIsInstance( + table, + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAuditTable, + ) + self.assertEqual(len(table.rows), 1) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) + self.assertEqual(table.rows[0].get_cell_value("role"), acm_models.GroupGroupMembership.MEMBER) + self.assertEqual( + table.rows[0].get_cell_value("note"), + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS, + ) + self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") + + class UploadWorkspaceAuthDomainAuditByWorkspaceTest(AnVILAPIMockTestMixin, TestCase): """Tests for the UploadWorkspaceAuthDomainAuditByWorkspace view.""" diff --git a/gregor_django/gregor_anvil/urls.py b/gregor_django/gregor_anvil/urls.py index 13aa2661..c65a5893 100644 --- a/gregor_django/gregor_anvil/urls.py +++ b/gregor_django/gregor_anvil/urls.py @@ -70,6 +70,7 @@ upload_workspace_auth_domain_audit_patterns = ( [ + path("all/", views.UploadWorkspaceAuthDomainAudit.as_view(), name="all"), path( "resolve////", views.UploadWorkspaceAuthDomainAuditResolve.as_view(), diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 31e5a20a..8b43b389 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -189,7 +189,7 @@ def get_context_data(self, **kwargs): class UploadWorkspaceSharingAudit(AnVILConsortiumManagerStaffViewRequired, TemplateView): - """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" + """View to audit UploadWorkspace sharing for all UploadWorkspaces.""" template_name = "gregor_anvil/upload_workspace_sharing_audit.html" @@ -391,6 +391,23 @@ def form_valid(self, form): return super().form_valid(form) +class UploadWorkspaceAuthDomainAudit(AnVILConsortiumManagerStaffViewRequired, TemplateView): + """View to audit UploadWorkspace auth domain membership for all UploadWorkspaces.""" + + template_name = "gregor_anvil/upload_workspace_sharing_audit.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Run the audit. + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + context["verified_table"] = audit.get_verified_table() + context["errors_table"] = audit.get_errors_table() + context["needs_action_table"] = audit.get_needs_action_table() + context["audit_results"] = audit + return context + + class UploadWorkspaceAuthDomainAuditByWorkspace(AnVILConsortiumManagerStaffEditRequired, DetailView): """View to audit UploadWorkspace sharing for a specific UploadWorkspace.""" From 2929709774df5ad2ba0af394103b04a08ebc2233 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 14:44:16 -0700 Subject: [PATCH 110/125] Include links to audit all views in management command When run_upload_workspace_audit detects an error, it now prints out a link to the page to audit sharing/auth domain membership for all UploadWorkspaces. --- .../management/commands/run_upload_workspace_audit.py | 5 +++-- gregor_django/gregor_anvil/tests/test_commands.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py b/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py index 6cd5a75e..fb7db4e9 100644 --- a/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py +++ b/gregor_django/gregor_anvil/management/commands/run_upload_workspace_audit.py @@ -2,6 +2,7 @@ from django.core.mail import send_mail from django.core.management.base import BaseCommand from django.template.loader import render_to_string +from django.urls import reverse from ...audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit @@ -20,13 +21,13 @@ def run_sharing_audit(self, *args, **options): self.stdout.write("Running UploadWorkspace sharing audit... ", ending="") audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.run_audit() - self._handle_audit_results(audit, "", **options) + self._handle_audit_results(audit, reverse("gregor_anvil:audit:upload_workspaces:sharing:all"), **options) def run_auth_domain_audit(self, *args, **options): self.stdout.write("Running UploadWorkspace auth domain audit... ", ending="") audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.run_audit() - self._handle_audit_results(audit, "", **options) + self._handle_audit_results(audit, reverse("gregor_anvil:audit:upload_workspaces:auth_domains:all"), **options) def _handle_audit_results(self, audit, url, **options): # Report errors and needs access. diff --git a/gregor_django/gregor_anvil/tests/test_commands.py b/gregor_django/gregor_anvil/tests/test_commands.py index 017dae7f..b39cbfbe 100644 --- a/gregor_django/gregor_anvil/tests/test_commands.py +++ b/gregor_django/gregor_anvil/tests/test_commands.py @@ -360,7 +360,7 @@ def test_auth_domain_audit_one_instance_needs_action_link_in_output(self): ) out = StringIO() call_command("run_upload_workspace_audit", "--no-color", stdout=out) - url = reverse("gregor_anvil:audit:upload_workspaces:auth_domain:all") + url = reverse("gregor_anvil:audit:upload_workspaces:auth_domains:all") self.assertIn(url, out.getvalue()) # Zero messages have been sent by default. self.assertEqual(len(mail.outbox), 0) From 6b61050a667d58423bc15466107ffdb7c2579839 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 15:57:42 -0700 Subject: [PATCH 111/125] Make sure new fields are shown on detail pages --- .../gregor_anvil/tests/test_views.py | 24 +++++++++++++++++++ .../gregor_anvil/uploadworkspace_detail.html | 12 +++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 5d063d23..f3a06712 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1262,6 +1262,14 @@ def test_view_with_two_objects(self): self.assertIn("table", response.context_data) self.assertEqual(len(response.context_data["table"].rows), 2) + def test_includes_date_ready_for_compute(self): + self.object.date_ready_for_compute = "2022-01-01" + self.object.save() + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.object.cycle)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + class AccountListTest(TestCase): def setUp(self): @@ -1339,6 +1347,14 @@ def test_contains_auth_domain_audit_button(self): ) self.assertContains(response, url) + def test_includes_date_qc_completed(self): + self.object.date_qc_completed = "2022-01-01" + self.object.save() + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + class UploadWorkspaceListTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" @@ -1907,6 +1923,14 @@ def test_contains_share_with_auth_domain_button(self): ) self.assertContains(response, url) + def test_includes_date_completed(self): + self.object.date_completed = "2022-01-01" + self.object.save() + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + class ReleaseWorkspaceDetailTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceDetail view using the ReleaseWorkspaceAdapter.""" diff --git a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html index f4b64f85..f4646b7b 100644 --- a/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html +++ b/gregor_django/templates/gregor_anvil/uploadworkspace_detail.html @@ -3,12 +3,12 @@ {% block workspace_data %}
    -
    Research Center
    {{ object.uploadworkspace.research_center }}
    -
    Consent group
    {{ object.uploadworkspace.consent_group }}
    -
    Upload cycle
    {{ object.uploadworkspace.upload_cycle }}
    +
    Research Center
    {{ workspace_data_object.research_center }}
    +
    Consent group
    {{ workspace_data_object.consent_group }}
    +
    Upload cycle
    {{ workspace_data_object.upload_cycle }}
    Date QC completed
    - {% if object.date_qc_completed %} - {{ object.date_qc_completed }} + {% if workspace_data_object.date_qc_completed %} + {{ workspace_data_object.date_qc_completed }} {% else %} — {% endif %} @@ -28,7 +28,7 @@

    - {{ object.uploadworkspace.consent_group.data_use_limitations }} + {{ workspace_data_object.consent_group.data_use_limitations }}
    From a4ccef09d9cc8b24536271274e481d79a4e197c0 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 16:01:28 -0700 Subject: [PATCH 112/125] Fix template object for audit results Accidentally had an old data_access_audit in the template, so some records were not getting rendered. --- gregor_django/templates/gregor_anvil/email_audit_report.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gregor_django/templates/gregor_anvil/email_audit_report.html b/gregor_django/templates/gregor_anvil/email_audit_report.html index fb1f74c4..fe112c9e 100644 --- a/gregor_django/templates/gregor_anvil/email_audit_report.html +++ b/gregor_django/templates/gregor_anvil/email_audit_report.html @@ -34,7 +34,7 @@

    Needs action - {{audit_results.needs_action|length }} record(s)

    Errors - {{audit_results.errors|length }} record(s)

      - {% for record in data_access_audit.errors %} + {% for record in audit_results.errors %}
    • {{ record|stringformat:'r' }}
    • {% endfor %}
    From 43e9e041139c2e1aa369e94d22f28f61721814d6 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 16:41:56 -0700 Subject: [PATCH 113/125] Move test to correct class and fix so it passes I accidentally put a test in the wrong class; move it to the UploadCycleDetailTest class, and fix the code so that it creates the object instead of relying on the test class to have it. --- gregor_django/gregor_anvil/tests/test_views.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index f3a06712..1f57c4dd 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -1187,6 +1187,13 @@ def test_contains_auth_domain_audit_button(self): url = reverse("gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", args=[obj.cycle]) self.assertContains(response, url) + def test_includes_date_ready_for_compute(self): + obj = self.model_factory.create(is_past=True, date_ready_for_compute="2022-01-01") + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + class UploadCycleListTest(TestCase): """Tests for the UploadCycleList view.""" @@ -1262,14 +1269,6 @@ def test_view_with_two_objects(self): self.assertIn("table", response.context_data) self.assertEqual(len(response.context_data["table"].rows), 2) - def test_includes_date_ready_for_compute(self): - self.object.date_ready_for_compute = "2022-01-01" - self.object.save() - self.client.force_login(self.user) - response = self.client.get(self.get_url(self.object.cycle)) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Jan. 1, 2022") - class AccountListTest(TestCase): def setUp(self): From a973804dc3088968b5a5addb4e76e910e5f9568d Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 17:15:40 -0700 Subject: [PATCH 114/125] Update upload workspace auth domains for RC uploaders RC uploaders should not have access until the upload cycle starts, and should lose access after QC is complete. Note that RC uploaders typically will have access via being in the member group. This commit likely breaks auditing for RC members groups, since we had to split the notes into separate notes for members vs. uploaders due to different access at different stages. --- .../upload_workspace_auth_domain_audit.py | 65 ++++++++++-- .../audit/upload_workspace_sharing_audit.py | 16 ++- .../gregor_anvil/tests/test_audit.py | 100 ++++++++---------- 3 files changed, 108 insertions(+), 73 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 9e99580f..2b42914a 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -128,12 +128,14 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): """A class to hold audit results for the GREGoR UploadWorkspace auth domain audit.""" # RC notes. - RC_BEFORE_COMBINED = ( - "RC uploader and member group should be members of the auth domain before the combined workspace is complete." + RC_FUTURE_CYCLE = "RC groups should not be in the auth domain until the upload cycle starts." + RC_UPLOADERS_BEFORE_QC = "RC uploader group should be a member of the auth domain before QC is complete." + RC_UPLOADERS_AFTER_QC = "RC uploader group should not be a member of the auth domain after QC is complete." + RC_MEMBERS_BEFORE_COMBINED = ( + "RC member group should be a member of the auth domain before the combined workspace is complete." ) - RC_AFTER_COMBINED = ( - "RC uploader and member group should not be direct members of the auth domain" - " after the combined workspace is complete." + RC_MEMBERS_AFTER_COMBINED = ( + "RC member group should not be a member of the auth domain after the combined workspace is complete." ) RC_NON_MEMBERS = "RC non-member group should always be a member of the auth domain." @@ -152,6 +154,7 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): # Other group notes. OTHER_GROUP = "This group should not have access to the auth domain." + UNEXPECTED_ADMIN = "Only the DCC admins group should be an admin of the auth domain." results_table_class = UploadWorkspaceAuthDomainAuditTable @@ -217,9 +220,9 @@ def audit_upload_workspace(self, upload_workspace): def audit_workspace_and_group(self, upload_workspace, managed_group): if managed_group == upload_workspace.research_center.uploader_group: - self._audit_workspace_and_group_for_rc(upload_workspace, managed_group) + self._audit_workspace_and_group_for_rc_uploaders(upload_workspace, managed_group) elif managed_group == upload_workspace.research_center.member_group: - self._audit_workspace_and_group_for_rc(upload_workspace, managed_group) + self._audit_workspace_and_group_for_rc_members(upload_workspace, managed_group) elif managed_group == upload_workspace.research_center.non_member_group: self._audit_workspace_and_group_for_rc_non_members(upload_workspace, managed_group) elif managed_group.name == settings.ANVIL_DCC_ADMINS_GROUP_NAME: @@ -237,13 +240,55 @@ def audit_workspace_and_group(self, upload_workspace, managed_group): else: self._audit_workspace_and_other_group(upload_workspace, managed_group) - def _audit_workspace_and_group_for_rc(self, upload_workspace, managed_group): + def _audit_workspace_and_group_for_rc_uploaders(self, upload_workspace, managed_group): + membership = self._get_current_membership(upload_workspace, managed_group) + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + } + + # Otherwise, proceed with other checks. + if upload_workspace.upload_cycle.is_future: + note = self.RC_FUTURE_CYCLE + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) + elif upload_workspace.upload_cycle.is_current: + note = self.RC_UPLOADERS_BEFORE_QC + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(ChangeToMember(note=note, **result_kwargs)) + elif membership: + self.verified.append(VerifiedMember(note=note, **result_kwargs)) + else: + self.needs_action.append(AddMember(note=note, **result_kwargs)) + elif upload_workspace.upload_cycle.is_past and not upload_workspace.date_qc_completed: + note = self.RC_UPLOADERS_BEFORE_QC + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(ChangeToMember(note=note, **result_kwargs)) + elif membership: + self.verified.append(VerifiedMember(note=note, **result_kwargs)) + else: + self.needs_action.append(AddMember(note=note, **result_kwargs)) + else: + note = self.RC_UPLOADERS_AFTER_QC + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) + + def _audit_workspace_and_group_for_rc_members(self, upload_workspace, managed_group): combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) membership = self._get_current_membership(upload_workspace, managed_group) if combined_workspace: - note = self.RC_AFTER_COMBINED + note = self.RC_MEMBERS_AFTER_COMBINED else: - note = self.RC_BEFORE_COMBINED + note = self.RC_MEMBERS_BEFORE_COMBINED result_kwargs = { "workspace": upload_workspace, "managed_group": managed_group, diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py index 54c5fe13..46c2d4a3 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py @@ -268,7 +268,7 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group """Audit access for a specific UploadWorkspace and RC uploader group. Sharing expectations: - - Write access to future upload cycle. + - No access to future upload cycle. - Write access before compute is enabled for current upload cycle. - Write+compute access after compute is enabled for current upload cycle. - Read access to past upload cycle workspaces before QC is completed. @@ -286,16 +286,12 @@ def _audit_workspace_and_rc_uploader_group(self, upload_workspace, managed_group if upload_cycle.is_future: note = self.RC_UPLOADERS_FUTURE_CYCLE - if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: - self.errors.append(ShareAsWriter(note=note, **audit_result_args)) - elif ( - current_sharing - and current_sharing.access == WorkspaceGroupSharing.WRITER - and not current_sharing.can_compute - ): - self.verified.append(VerifiedShared(note=note, **audit_result_args)) + if not current_sharing: + self.verified.append(VerifiedNotShared(note=note, **audit_result_args)) + elif current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: + self.errors.append(StopSharing(note=note, **audit_result_args)) else: - self.needs_action.append(ShareAsWriter(note=note, **audit_result_args)) + self.needs_action.append(StopSharing(note=note, **audit_result_args)) elif upload_cycle.is_current and not upload_cycle.date_ready_for_compute: note = self.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE if current_sharing and current_sharing.access == WorkspaceGroupSharing.OWNER: diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index f77b4573..79b839a2 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -352,11 +352,11 @@ def test_uploaders_shared_as_writer_no_compute(self): ) audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) - self.assertEqual(len(audit.verified), 1) - self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) - record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) @@ -367,11 +367,11 @@ def test_uploaders_shared_as_writer_no_compute(self): def test_uploaders_not_shared(self): audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) - self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, None) @@ -392,7 +392,7 @@ def test_uploaders_shared_as_writer_can_compute(self): self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) @@ -410,7 +410,7 @@ def test_uploaders_shared_as_reader(self): self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) @@ -428,7 +428,7 @@ def test_uploaders_shared_as_owner(self): self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 1) record = audit.errors[0] - self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsWriter) + self.assertIsInstance(record, upload_workspace_sharing_audit.StopSharing) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_sharing_instance, sharing) @@ -4051,17 +4051,15 @@ def setUp(self): def test_rc_uploaders_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) - self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertIsNone(record.current_membership_instance) - self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED - ) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_rc_uploaders_member(self): membership = GroupGroupMembershipFactory.create( @@ -4071,17 +4069,15 @@ def test_rc_uploaders_member(self): ) audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) - self.assertEqual(len(audit.verified), 1) - self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) - record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED - ) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_rc_uploaders_admin(self): membership = GroupGroupMembershipFactory.create( @@ -4095,13 +4091,11 @@ def test_rc_uploaders_admin(self): self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 1) record = audit.errors[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED - ) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_rc_members_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() @@ -4587,7 +4581,7 @@ def test_rc_uploaders_not_member(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_uploaders_member(self): @@ -4607,7 +4601,7 @@ def test_rc_uploaders_member(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_uploaders_admin(self): @@ -4627,7 +4621,7 @@ def test_rc_uploaders_admin(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_members_not_member(self): @@ -5116,7 +5110,7 @@ def test_rc_uploaders_not_member(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_uploaders_member(self): @@ -5136,7 +5130,7 @@ def test_rc_uploaders_member(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_uploaders_admin(self): @@ -5156,7 +5150,7 @@ def test_rc_uploaders_admin(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_members_not_member(self): @@ -5598,7 +5592,7 @@ def test_rc_uploaders_not_member(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_uploaders_member(self): @@ -5618,7 +5612,7 @@ def test_rc_uploaders_member(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_uploaders_admin(self): @@ -5638,7 +5632,7 @@ def test_rc_uploaders_admin(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_BEFORE_QC ) def test_rc_members_not_member(self): @@ -6121,16 +6115,16 @@ def setUp(self): def test_rc_uploaders_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) - self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC ) def test_rc_uploaders_member(self): @@ -6141,16 +6135,16 @@ def test_rc_uploaders_member(self): ) audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_uploader_group) - self.assertEqual(len(audit.verified), 1) - self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) - record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC ) def test_rc_uploaders_admin(self): @@ -6165,12 +6159,12 @@ def test_rc_uploaders_admin(self): self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 1) record = audit.errors[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC ) def test_rc_members_not_member(self): @@ -6665,7 +6659,7 @@ def test_rc_uploaders_not_member(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC ) def test_rc_uploaders_member(self): @@ -6685,7 +6679,7 @@ def test_rc_uploaders_member(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC ) def test_rc_uploaders_admin(self): @@ -6705,7 +6699,7 @@ def test_rc_uploaders_admin(self): self.assertEqual(record.managed_group, self.rc_uploader_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_UPLOADERS_AFTER_QC ) def test_rc_members_not_member(self): From a8dd36c4c7e1663eda6511fb9449291b126ec4c5 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 17:28:30 -0700 Subject: [PATCH 115/125] Update upload workspace audit for RC members group The RC members group should not be added to the auth domain until after the upload cycle starts. --- .../upload_workspace_auth_domain_audit.py | 41 +++++----- .../gregor_anvil/tests/test_audit.py | 75 ++++++++----------- 2 files changed, 53 insertions(+), 63 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index 2b42914a..b24af35d 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -283,33 +283,38 @@ def _audit_workspace_and_group_for_rc_uploaders(self, upload_workspace, managed_ self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) def _audit_workspace_and_group_for_rc_members(self, upload_workspace, managed_group): - combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) membership = self._get_current_membership(upload_workspace, managed_group) - if combined_workspace: - note = self.RC_MEMBERS_AFTER_COMBINED - else: - note = self.RC_MEMBERS_BEFORE_COMBINED + combined_workspace = self._get_combined_workspace(upload_workspace.upload_cycle) result_kwargs = { "workspace": upload_workspace, "managed_group": managed_group, "current_membership_instance": membership, - "note": note, } - if not combined_workspace and not membership: - self.needs_action.append(AddMember(**result_kwargs)) - elif not combined_workspace and membership: - if membership.role == GroupGroupMembership.MEMBER: - self.verified.append(VerifiedMember(**result_kwargs)) + if upload_workspace.upload_cycle.is_future: + note = self.RC_FUTURE_CYCLE + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) else: - self.errors.append(ChangeToMember(**result_kwargs)) - elif combined_workspace and not membership: - self.verified.append(VerifiedNotMember(**result_kwargs)) - elif combined_workspace and membership: - if membership.role == GroupGroupMembership.ADMIN: - self.errors.append(Remove(**result_kwargs)) + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) + elif not combined_workspace: + note = self.RC_MEMBERS_BEFORE_COMBINED + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(ChangeToMember(note=note, **result_kwargs)) + elif membership: + self.verified.append(VerifiedMember(note=note, **result_kwargs)) else: - self.needs_action.append(Remove(**result_kwargs)) + self.needs_action.append(AddMember(note=note, **result_kwargs)) + else: + note = self.RC_MEMBERS_AFTER_COMBINED + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) def _audit_workspace_and_group_for_rc_non_members(self, upload_workspace, managed_group): membership = self._get_current_membership(upload_workspace, managed_group) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index 79b839a2..b9dce6c8 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -199,16 +199,13 @@ def test_one_upload_workspace_rc_upload_group(self): upload_workspace = factories.UploadWorkspaceFactory.create( research_center__uploader_group=group, upload_cycle__is_future=True ) - WorkspaceGroupSharingFactory.create( - workspace=upload_workspace.workspace, group=group, access=WorkspaceGroupSharing.WRITER - ) audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() audit.run_audit() self.assertEqual(len(audit.verified), 1) self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared self.assertEqual(len(audit.errors), 0) record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedNotShared) self.assertEqual(record.workspace, upload_workspace) self.assertEqual(record.managed_group, group) @@ -3836,16 +3833,13 @@ def test_one_upload_workspace_rc_member_group(self): upload_workspace = factories.UploadWorkspaceFactory.create( research_center__member_group=group, upload_cycle__is_future=True ) - GroupGroupMembershipFactory.create( - parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group - ) audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.run_audit() self.assertEqual(len(audit.verified), 1) self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) self.assertEqual(record.workspace, upload_workspace) self.assertEqual(record.managed_group, group) @@ -3854,16 +3848,13 @@ def test_one_upload_workspace_rc_upload_group(self): upload_workspace = factories.UploadWorkspaceFactory.create( research_center__uploader_group=group, upload_cycle__is_future=True ) - GroupGroupMembershipFactory.create( - parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group - ) audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.run_audit() self.assertEqual(len(audit.verified), 1) self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) self.assertEqual(record.workspace, upload_workspace) self.assertEqual(record.managed_group, group) @@ -4100,17 +4091,15 @@ def test_rc_uploaders_admin(self): def test_rc_members_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) - self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_member_group) self.assertIsNone(record.current_membership_instance) - self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED - ) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_rc_members_member(self): membership = GroupGroupMembershipFactory.create( @@ -4120,17 +4109,15 @@ def test_rc_members_member(self): ) audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_member_group) - self.assertEqual(len(audit.verified), 1) - self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) - record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED - ) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_rc_members_admin(self): membership = GroupGroupMembershipFactory.create( @@ -4144,13 +4131,11 @@ def test_rc_members_admin(self): self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 1) record = audit.errors[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED - ) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_rc_non_members_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() @@ -4636,7 +4621,7 @@ def test_rc_members_not_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_members_member(self): @@ -4656,7 +4641,7 @@ def test_rc_members_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_members_admin(self): @@ -4676,7 +4661,7 @@ def test_rc_members_admin(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_non_members_not_member(self): @@ -5165,7 +5150,7 @@ def test_rc_members_not_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_members_member(self): @@ -5185,7 +5170,7 @@ def test_rc_members_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_members_admin(self): @@ -5205,7 +5190,7 @@ def test_rc_members_admin(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_dcc_admins_not_member(self): @@ -5647,7 +5632,7 @@ def test_rc_members_not_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_members_member(self): @@ -5667,7 +5652,7 @@ def test_rc_members_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_members_admin(self): @@ -5687,7 +5672,7 @@ def test_rc_members_admin(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_non_members_not_member(self): @@ -6179,7 +6164,7 @@ def test_rc_members_not_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_members_member(self): @@ -6199,7 +6184,7 @@ def test_rc_members_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_members_admin(self): @@ -6219,7 +6204,7 @@ def test_rc_members_admin(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_BEFORE_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) def test_rc_non_members_not_member(self): @@ -6714,7 +6699,7 @@ def test_rc_members_not_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertIsNone(record.current_membership_instance) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_AFTER_COMBINED ) def test_rc_members_member(self): @@ -6734,7 +6719,7 @@ def test_rc_members_member(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_AFTER_COMBINED ) def test_rc_members_admin(self): @@ -6754,7 +6739,7 @@ def test_rc_members_admin(self): self.assertEqual(record.managed_group, self.rc_member_group) self.assertEqual(record.current_membership_instance, membership) self.assertEqual( - record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_AFTER_COMBINED + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_AFTER_COMBINED ) def test_rc_non_members_not_member(self): From 92f1dfe48d200a7905362188aa2cf5c2731b8251 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 17:41:26 -0700 Subject: [PATCH 116/125] Update upload workspace audits for RC non-members RC non-members should only have access to the workspace after an upload cycle starts. --- .../upload_workspace_auth_domain_audit.py | 51 ++++--- .../gregor_anvil/tests/test_audit.py | 132 ++++++++++++++---- 2 files changed, 128 insertions(+), 55 deletions(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py index b24af35d..ebb98e34 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_auth_domain_audit.py @@ -137,7 +137,9 @@ class UploadWorkspaceAuthDomainAudit(GREGoRAudit): RC_MEMBERS_AFTER_COMBINED = ( "RC member group should not be a member of the auth domain after the combined workspace is complete." ) - RC_NON_MEMBERS = "RC non-member group should always be a member of the auth domain." + RC_NON_MEMBERS_AFTER_START = ( + "RC non-member group should be a member of the auth domain for current and past upload cycles." + ) # DCC notes. DCC_ADMINS = "DCC admin group should always be an admin of the auth domain." @@ -318,33 +320,28 @@ def _audit_workspace_and_group_for_rc_members(self, upload_workspace, managed_gr def _audit_workspace_and_group_for_rc_non_members(self, upload_workspace, managed_group): membership = self._get_current_membership(upload_workspace, managed_group) - if not membership: - self.needs_action.append( - AddMember( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_NON_MEMBERS, - current_membership_instance=membership, - ) - ) - elif membership.role == GroupGroupMembership.MEMBER: - self.verified.append( - VerifiedMember( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_NON_MEMBERS, - current_membership_instance=membership, - ) - ) + result_kwargs = { + "workspace": upload_workspace, + "managed_group": managed_group, + "current_membership_instance": membership, + } + + if upload_workspace.upload_cycle.is_future: + note = self.RC_FUTURE_CYCLE + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(Remove(note=note, **result_kwargs)) + elif membership: + self.needs_action.append(Remove(note=note, **result_kwargs)) + else: + self.verified.append(VerifiedNotMember(note=note, **result_kwargs)) else: - self.errors.append( - ChangeToMember( - workspace=upload_workspace, - managed_group=managed_group, - note=self.RC_NON_MEMBERS, - current_membership_instance=membership, - ) - ) + note = self.RC_NON_MEMBERS_AFTER_START + if membership and membership.role == GroupGroupMembership.ADMIN: + self.errors.append(ChangeToMember(note=note, **result_kwargs)) + elif membership: + self.verified.append(VerifiedMember(note=note, **result_kwargs)) + else: + self.needs_action.append(AddMember(note=note, **result_kwargs)) def _audit_workspace_and_group_for_dcc_admin(self, upload_workspace, managed_group): membership = self._get_current_membership(upload_workspace, managed_group) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index b9dce6c8..a25d3ee8 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -3863,16 +3863,13 @@ def test_one_upload_workspace_rc_nonmember_group(self): upload_workspace = factories.UploadWorkspaceFactory.create( research_center__non_member_group=group, upload_cycle__is_future=True ) - GroupGroupMembershipFactory.create( - parent_group=upload_workspace.workspace.authorization_domains.first(), child_group=group - ) audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.run_audit() self.assertEqual(len(audit.verified), 1) self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) self.assertEqual(record.workspace, upload_workspace) self.assertEqual(record.managed_group, group) @@ -4140,15 +4137,15 @@ def test_rc_members_admin(self): def test_rc_non_members_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) - self.assertEqual(len(audit.verified), 0) - self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) - record = audit.needs_action[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedNotMember) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertIsNone(record.current_membership_instance) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_rc_non_members_member(self): membership = GroupGroupMembershipFactory.create( @@ -4158,15 +4155,15 @@ def test_rc_non_members_member(self): ) audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) - self.assertEqual(len(audit.verified), 1) - self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) self.assertEqual(len(audit.errors), 0) - record = audit.verified[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_rc_non_members_admin(self): membership = GroupGroupMembershipFactory.create( @@ -4180,11 +4177,11 @@ def test_rc_non_members_admin(self): self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 1) record = audit.errors[0] - self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertIsInstance(record, upload_workspace_auth_domain_audit.Remove) self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_FUTURE_CYCLE) def test_dcc_admins_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() @@ -4675,7 +4672,9 @@ def test_rc_non_members_not_member(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertIsNone(record.current_membership_instance) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_rc_non_members_member(self): membership = GroupGroupMembershipFactory.create( @@ -4693,7 +4692,9 @@ def test_rc_non_members_member(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_rc_non_members_admin(self): membership = GroupGroupMembershipFactory.create( @@ -4711,7 +4712,9 @@ def test_rc_non_members_admin(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_dcc_admins_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() @@ -5193,6 +5196,61 @@ def test_rc_members_admin(self): record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_MEMBERS_BEFORE_COMBINED ) + def test_rc_non_members_not_member(self): + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_member(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.MEMBER, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + + def test_rc_non_members_admin(self): + membership = GroupGroupMembershipFactory.create( + parent_group=self.upload_workspace.workspace.authorization_domains.first(), + child_group=self.rc_non_member_group, + role=GroupGroupMembership.ADMIN, + ) + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.audit_workspace_and_group(self.upload_workspace, self.rc_non_member_group) + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 1) + record = audit.errors[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.ChangeToMember) + self.assertEqual(record.workspace, self.upload_workspace) + self.assertEqual(record.managed_group, self.rc_non_member_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) + def test_dcc_admins_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() audit.audit_workspace_and_group(self.upload_workspace, self.dcc_admin_group) @@ -5686,7 +5744,9 @@ def test_rc_non_members_not_member(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertIsNone(record.current_membership_instance) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_rc_non_members_member(self): membership = GroupGroupMembershipFactory.create( @@ -5704,7 +5764,9 @@ def test_rc_non_members_member(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_rc_non_members_admin(self): membership = GroupGroupMembershipFactory.create( @@ -5722,7 +5784,9 @@ def test_rc_non_members_admin(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_dcc_admins_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() @@ -6218,7 +6282,9 @@ def test_rc_non_members_not_member(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertIsNone(record.current_membership_instance) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_rc_non_members_member(self): membership = GroupGroupMembershipFactory.create( @@ -6236,7 +6302,9 @@ def test_rc_non_members_member(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_rc_non_members_admin(self): membership = GroupGroupMembershipFactory.create( @@ -6254,7 +6322,9 @@ def test_rc_non_members_admin(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_dcc_admins_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() @@ -6753,7 +6823,9 @@ def test_rc_non_members_not_member(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertIsNone(record.current_membership_instance) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_rc_non_members_member(self): membership = GroupGroupMembershipFactory.create( @@ -6771,7 +6843,9 @@ def test_rc_non_members_member(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_rc_non_members_admin(self): membership = GroupGroupMembershipFactory.create( @@ -6789,7 +6863,9 @@ def test_rc_non_members_admin(self): self.assertEqual(record.workspace, self.upload_workspace) self.assertEqual(record.managed_group, self.rc_non_member_group) self.assertEqual(record.current_membership_instance, membership) - self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS) + self.assertEqual( + record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.RC_NON_MEMBERS_AFTER_START + ) def test_dcc_admins_not_member(self): audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() From edb50a6dde023ef290ba8c399f6374bc58934f81 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 17:55:52 -0700 Subject: [PATCH 117/125] Wording change for RC future cycles note --- .../gregor_anvil/audit/upload_workspace_sharing_audit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py index 46c2d4a3..249c7a24 100644 --- a/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py +++ b/gregor_django/gregor_anvil/audit/upload_workspace_sharing_audit.py @@ -150,7 +150,7 @@ class UploadWorkspaceSharingAudit(GREGoRAudit): """A class to hold audit results for the GREGoR UploadWorkspace audit.""" # RC uploader statues. - RC_UPLOADERS_FUTURE_CYCLE = "Uploaders should have write access for future cycles." + RC_UPLOADERS_FUTURE_CYCLE = "Uploaders should not have access to future cycles." RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE = ( "Uploaders should have write access before compute is enabled for this upload cycle." ) From 367696da91b98d688609964d129881b8170d0169 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 28 Aug 2024 17:56:25 -0700 Subject: [PATCH 118/125] Update view tests for changes in audit behavior Mostly these affected the timing of the upload cycle and when to grant writer access to RC uploaders. --- .../gregor_anvil/tests/test_views.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 1f57c4dd..fa752a8a 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -2532,7 +2532,7 @@ def test_context_needs_action_table_share_as_writer(self): """needs_action_table shows a record when audit finds that access needs to be granted.""" group = acm_factories.ManagedGroupFactory.create() upload_workspace = factories.UploadWorkspaceFactory.create( - upload_cycle__is_future=True, + upload_cycle__is_current=True, research_center__uploader_group=group, ) # Share with the auth domain to prevent that audit error. @@ -2556,7 +2556,7 @@ def test_context_needs_action_table_share_as_writer(self): self.assertIsNone(table.rows[0].get_cell_value("access")) self.assertEqual( table.rows[0].get_cell_value("note"), - upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, ) self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") @@ -2871,21 +2871,22 @@ def test_context_needs_action_table_share_as_reader(self): def test_context_needs_action_table_share_as_writer(self): """needs_action_table shows a record when audit finds that access needs to be granted.""" group = acm_factories.ManagedGroupFactory.create() - rc = self.upload_workspace.research_center - rc.uploader_group = group - rc.save() + upload_workspace = factories.UploadWorkspaceFactory.create( + upload_cycle__is_current=True, + research_center__uploader_group=group, + ) # Share with the auth domain to prevent that audit error. acm_factories.WorkspaceGroupSharingFactory.create( - workspace=self.upload_workspace.workspace, - group=self.upload_workspace.workspace.authorization_domains.first(), + workspace=upload_workspace.workspace, + group=upload_workspace.workspace.authorization_domains.first(), access=acm_models.WorkspaceGroupSharing.READER, ) # Check the table in the context. self.client.force_login(self.user) response = self.client.get( self.get_url( - self.upload_workspace.workspace.billing_project.name, - self.upload_workspace.workspace.name, + upload_workspace.workspace.billing_project.name, + upload_workspace.workspace.name, ) ) self.assertIn("needs_action_table", response.context_data) @@ -2895,12 +2896,12 @@ def test_context_needs_action_table_share_as_writer(self): upload_workspace_sharing_audit.UploadWorkspaceSharingAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual(table.rows[0].get_cell_value("workspace"), self.upload_workspace) + self.assertEqual(table.rows[0].get_cell_value("workspace"), upload_workspace) self.assertEqual(table.rows[0].get_cell_value("managed_group"), group) self.assertIsNone(table.rows[0].get_cell_value("access")) self.assertEqual( table.rows[0].get_cell_value("note"), - upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, ) self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") @@ -3227,7 +3228,7 @@ def test_context_needs_action_table_share_as_writer(self): """needs_action_table shows a record when audit finds that access needs to be granted.""" group = acm_factories.ManagedGroupFactory.create() upload_workspace = factories.UploadWorkspaceFactory.create( - upload_cycle=self.upload_cycle, + upload_cycle__is_current=True, research_center__uploader_group=group, ) # Share with the auth domain to prevent that audit error. @@ -3238,7 +3239,7 @@ def test_context_needs_action_table_share_as_writer(self): ) # Check the table in the context. self.client.force_login(self.user) - response = self.client.get(self.get_url(self.upload_cycle.cycle)) + response = self.client.get(self.get_url(upload_workspace.upload_cycle.cycle)) self.assertIn("needs_action_table", response.context_data) table = response.context_data["needs_action_table"] self.assertIsInstance( @@ -3251,7 +3252,7 @@ def test_context_needs_action_table_share_as_writer(self): self.assertIsNone(table.rows[0].get_cell_value("access")) self.assertEqual( table.rows[0].get_cell_value("note"), - upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, ) self.assertNotEqual(table.rows[0].get_cell_value("action"), "—") @@ -3568,7 +3569,7 @@ def test_get_share_as_reader(self): def test_get_share_as_writer(self): group = acm_factories.ManagedGroupFactory.create() upload_workspace = factories.UploadWorkspaceFactory.create( - upload_cycle__is_future=True, + upload_cycle__is_current=True, research_center__uploader_group=group, ) self.client.force_login(self.user) @@ -3581,7 +3582,8 @@ def test_get_share_as_writer(self): self.assertEqual(audit_result.workspace, upload_workspace) self.assertEqual(audit_result.managed_group, group) self.assertEqual( - audit_result.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_FUTURE_CYCLE + audit_result.note, + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.RC_UPLOADERS_CURRENT_CYCLE_BEFORE_COMPUTE, ) def test_get_share_with_compute(self): @@ -3714,7 +3716,7 @@ def test_post_new_share_as_writer(self): upload_workspace = factories.UploadWorkspaceFactory.create( workspace__billing_project__name="test-bp", workspace__name="test-ws", - upload_cycle__is_future=True, + upload_cycle__is_current=True, research_center__uploader_group=group, ) # Add the mocked API response. @@ -3889,7 +3891,7 @@ def test_post_update_share_as_writer(self): upload_workspace = factories.UploadWorkspaceFactory.create( workspace__billing_project__name="test-bp", workspace__name="test-ws", - upload_cycle__is_future=True, + upload_cycle__is_current=True, research_center__uploader_group=group, ) date_created = timezone.now() - timedelta(weeks=3) @@ -4049,7 +4051,7 @@ def test_post_new_share_as_writer_htmx(self): upload_workspace = factories.UploadWorkspaceFactory.create( workspace__billing_project__name="test-bp", workspace__name="test-ws", - upload_cycle__is_future=True, + upload_cycle__is_current=True, research_center__uploader_group=group, ) # Add the mocked API response. @@ -4219,7 +4221,7 @@ def test_post_new_share_as_writer_anvil_api_error(self): upload_workspace = factories.UploadWorkspaceFactory.create( workspace__billing_project__name="test-bp", workspace__name="test-ws", - upload_cycle__is_future=True, + upload_cycle__is_current=True, research_center__uploader_group=group, ) # Add the mocked API response. @@ -4374,7 +4376,7 @@ def test_post_new_share_as_writer_anvil_api_error_htmx(self): upload_workspace = factories.UploadWorkspaceFactory.create( workspace__billing_project__name="test-bp", workspace__name="test-ws", - upload_cycle__is_future=True, + upload_cycle__is_current=True, research_center__uploader_group=group, ) # Add the mocked API response. @@ -4492,7 +4494,7 @@ def test_post_new_share_as_writer_group_not_found_on_anvil_htmx(self): upload_workspace = factories.UploadWorkspaceFactory.create( workspace__billing_project__name="test-bp", workspace__name="test-ws", - upload_cycle__is_future=True, + upload_cycle__is_current=True, research_center__uploader_group=group, ) acls = [ From bd26e581971274544b4ddfdcf737ec232fb923b8 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 29 Aug 2024 10:59:11 -0700 Subject: [PATCH 119/125] Improve test coverage for upload workspace audits --- .../gregor_anvil/tests/test_audit.py | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/gregor_django/gregor_anvil/tests/test_audit.py b/gregor_django/gregor_anvil/tests/test_audit.py index a25d3ee8..83dc8ff5 100644 --- a/gregor_django/gregor_anvil/tests/test_audit.py +++ b/gregor_django/gregor_anvil/tests/test_audit.py @@ -13,6 +13,7 @@ from django.test import TestCase, override_settings from faker import Faker +from .. import models from ..audit import upload_workspace_auth_domain_audit, upload_workspace_sharing_audit from ..audit.base import GREGoRAudit, GREGoRAuditResult from ..tests import factories @@ -319,6 +320,78 @@ def test_one_upload_workspace_other_group_not_shared(self): self.assertEqual(len(audit.needs_action), 1) # auth domain is not shared self.assertEqual(len(audit.errors), 0) + def test_two_upload_workspaces(self): + """Audit works with two UploadWorkspaces.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace_1.workspace, + group=upload_workspace_1.workspace.authorization_domains.first(), + ) + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace_1) + self.assertEqual(record.managed_group, upload_workspace_1.workspace.authorization_domains.first()) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace_2) + self.assertEqual(record.managed_group, upload_workspace_2.workspace.authorization_domains.first()) + self.assertIsNone(record.current_sharing_instance) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_queryset(self): + """Audit only runs on the specified queryset of dbGaPApplications.""" + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + sharing = WorkspaceGroupSharingFactory.create( + workspace=upload_workspace_1.workspace, + group=upload_workspace_1.workspace.authorization_domains.first(), + ) + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + # First application + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit( + queryset=models.UploadWorkspace.objects.filter(pk=upload_workspace_1.pk) + ) + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.VerifiedShared) + self.assertEqual(record.workspace, upload_workspace_1) + self.assertEqual(record.managed_group, upload_workspace_1.workspace.authorization_domains.first()) + self.assertEqual(record.current_sharing_instance, sharing) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + # Second application + audit = upload_workspace_sharing_audit.UploadWorkspaceSharingAudit( + queryset=models.UploadWorkspace.objects.filter(pk=upload_workspace_2.pk) + ) + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_sharing_audit.ShareAsReader) + self.assertEqual(record.workspace, upload_workspace_2) + self.assertEqual(record.managed_group, upload_workspace_2.workspace.authorization_domains.first()) + self.assertIsNone(record.current_sharing_instance) + self.assertEqual(record.note, upload_workspace_sharing_audit.UploadWorkspaceSharingAudit.AUTH_DOMAIN_AS_READER) + + def test_queryset_wrong_class(self): + """Raises ValueError if queryset is not a QuerySet.""" + with self.assertRaises(ValueError): + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit(queryset="foo") + with self.assertRaises(ValueError): + upload_workspace_sharing_audit.UploadWorkspaceSharingAudit( + queryset=models.CombinedConsortiumDataWorkspace.objects.all() + ) + class UploadWorkspaceSharingAuditFutureCycleTest(TestCase): """Tests for the `UploadWorkspaceSharingAudit` class for future cycle UploadWorkspaces. @@ -4010,6 +4083,82 @@ def test_one_upload_workspace_other_group_not_member(self): self.assertEqual(len(audit.needs_action), 0) self.assertEqual(len(audit.errors), 0) + def test_two_upload_workspaces(self): + """Audit works with two UploadWorkspaces.""" + admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + membership = GroupGroupMembershipFactory.create( + parent_group=upload_workspace_1.workspace.authorization_domains.first(), + child_group=admin_group, + role=GroupGroupMembership.ADMIN, + ) + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit() + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, upload_workspace_1) + self.assertEqual(record.managed_group, admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, upload_workspace_2) + self.assertEqual(record.managed_group, admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_queryset(self): + """Audit only runs on the specified queryset of UploadWorkspaces.""" + admin_group = ManagedGroupFactory.create(name=settings.ANVIL_DCC_ADMINS_GROUP_NAME) + upload_workspace_1 = factories.UploadWorkspaceFactory.create() + membership = GroupGroupMembershipFactory.create( + parent_group=upload_workspace_1.workspace.authorization_domains.first(), + child_group=admin_group, + role=GroupGroupMembership.ADMIN, + ) + upload_workspace_2 = factories.UploadWorkspaceFactory.create() + # First application + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=models.UploadWorkspace.objects.filter(pk=upload_workspace_1.pk) + ) + audit.run_audit() + self.assertEqual(len(audit.verified), 1) + self.assertEqual(len(audit.needs_action), 0) + self.assertEqual(len(audit.errors), 0) + record = audit.verified[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.VerifiedAdmin) + self.assertEqual(record.workspace, upload_workspace_1) + self.assertEqual(record.managed_group, admin_group) + self.assertEqual(record.current_membership_instance, membership) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + # Second application + audit = upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=models.UploadWorkspace.objects.filter(pk=upload_workspace_2.pk) + ) + audit.run_audit() + self.assertEqual(len(audit.verified), 0) + self.assertEqual(len(audit.needs_action), 1) + self.assertEqual(len(audit.errors), 0) + record = audit.needs_action[0] + self.assertIsInstance(record, upload_workspace_auth_domain_audit.AddAdmin) + self.assertEqual(record.workspace, upload_workspace_2) + self.assertEqual(record.managed_group, admin_group) + self.assertIsNone(record.current_membership_instance) + self.assertEqual(record.note, upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit.DCC_ADMINS) + + def test_queryset_wrong_class(self): + """Raises ValueError if queryset is not a QuerySet.""" + with self.assertRaises(ValueError): + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit(queryset="foo") + with self.assertRaises(ValueError): + upload_workspace_auth_domain_audit.UploadWorkspaceAuthDomainAudit( + queryset=models.CombinedConsortiumDataWorkspace.objects.all() + ) + class UploadWorkspaceAuthDomainAuditFutureCycleTest(TestCase): """Tests for the `UploadWorkspaceAuthDomainAudit` class for future cycle UploadWorkspaces.""" From 3dd2cb85dbb5a9cad47962fbe865345f96e918b7 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Thu, 29 Aug 2024 14:19:16 -0700 Subject: [PATCH 120/125] Update default dates for date_qc_completed and date_ready_for_compute 2 weeks and 4 weeks, respectively, are more in line with approximate timeframes for past upload cycles. --- .../0028_populate_uploadcycle_date_ready_for_compute.py | 2 +- .../0029_populate_uploadworkspace_date_qc_completed.py | 2 +- gregor_django/gregor_anvil/tests/test_migrations.py | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py index b9728c1b..db6819f0 100644 --- a/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py +++ b/gregor_django/gregor_anvil/migrations/0028_populate_uploadcycle_date_ready_for_compute.py @@ -10,7 +10,7 @@ def populate_uploadcycle_date_ready_for_compute(apps, schema_editor): UploadCycle = apps.get_model("gregor_anvil", "UploadCycle") # Create one UploadCycle for each unique version. for row in UploadCycle.objects.all(): - assumed_date_ready_for_compute = row.start_date + timedelta(days=7) + assumed_date_ready_for_compute = row.start_date + timedelta(weeks=4) if row.end_date <= timezone.localdate(): row.date_ready_for_compute = assumed_date_ready_for_compute row.full_clean() diff --git a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py index e860b22c..aa06ff42 100644 --- a/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py +++ b/gregor_django/gregor_anvil/migrations/0029_populate_uploadworkspace_date_qc_completed.py @@ -9,7 +9,7 @@ def populate_uploadworkspace_date_qc_complete(apps, schema_editor): UploadWorkspace = apps.get_model("gregor_anvil", "UploadWorkspace") # Create one UploadWorkspace for each unique version. for row in UploadWorkspace.objects.all(): - assumed_date_qc_completed = row.upload_cycle.end_date + timedelta(days=7) + assumed_date_qc_completed = row.upload_cycle.end_date + timedelta(weeks=2) if assumed_date_qc_completed <= timezone.localdate(): row.date_qc_completed = assumed_date_qc_completed row.full_clean() diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index 62dca0e9..fb377f31 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -352,7 +352,7 @@ def prepare(self): def test_date_completed(self): UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_past.pk) - self.assertEqual(upload_cycle.date_ready_for_compute, upload_cycle.start_date + timedelta(days=7)) + self.assertEqual(upload_cycle.date_ready_for_compute, upload_cycle.start_date + timedelta(weeks=4)) upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_current.pk) self.assertIsNone(upload_cycle.date_ready_for_compute) upload_cycle = UploadCycle.objects.get(pk=self.upload_cycle_future.pk) @@ -432,7 +432,9 @@ def prepare(self): def test_date_qc_completed(self): UploadWorkspace = self.old_state.apps.get_model("gregor_anvil", "UploadWorkspace") upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_past.pk) - self.assertEqual(upload_workspace.date_qc_completed, upload_workspace.upload_cycle.end_date + timedelta(days=7)) + self.assertEqual( + upload_workspace.date_qc_completed, upload_workspace.upload_cycle.end_date + timedelta(weeks=2) + ) upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_current.pk) self.assertIsNone(upload_workspace.date_qc_completed) upload_workspace = UploadWorkspace.objects.get(pk=self.upload_workspace_future.pk) @@ -514,7 +516,6 @@ def prepare(self): workspace=workspace, ) - def test_date_completed(self): CombinedConsortiumDataWorkspace = self.new_state.apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") workspace = CombinedConsortiumDataWorkspace.objects.get(pk=self.combined_workspace_shared.pk) From bf056caa819b401cd4ff4cb04968a3ae81473eef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:11:36 +0000 Subject: [PATCH 121/125] Bump factory-boy from 3.3.0 to 3.3.1 Bumps [factory-boy](https://github.com/FactoryBoy/factory_boy) from 3.3.0 to 3.3.1. - [Changelog](https://github.com/FactoryBoy/factory_boy/blob/master/docs/changelog.rst) - [Commits](https://github.com/FactoryBoy/factory_boy/compare/3.3.0...3.3.1) --- updated-dependencies: - dependency-name: factory-boy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/test-requirements.txt b/requirements/test-requirements.txt index cc792a59..cd16f745 100644 --- a/requirements/test-requirements.txt +++ b/requirements/test-requirements.txt @@ -25,7 +25,7 @@ exceptiongroup==1.2.0 # via pytest execnet==2.1.1 # via pytest-xdist -factory-boy==3.3.0 +factory-boy==3.3.1 # via -r requirements/test-requirements.in faker==23.2.1 # via factory-boy From d876275f64e6f718a71c7d9d858b03f1b9431035 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:12:09 +0000 Subject: [PATCH 122/125] Bump django-dbbackup from 4.1.0 to 4.2.1 Bumps [django-dbbackup](https://github.com/jazzband/django-dbbackup) from 4.1.0 to 4.2.1. - [Release notes](https://github.com/jazzband/django-dbbackup/releases) - [Changelog](https://github.com/jazzband/django-dbbackup/blob/master/docs/changelog.rst) - [Commits](https://github.com/jazzband/django-dbbackup/compare/4.1.0...4.2.1) --- updated-dependencies: - dependency-name: django-dbbackup dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a54d6583..29fa5ab1 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -64,7 +64,7 @@ django-crispy-forms==2.3 # -r requirements/requirements.in # crispy-bootstrap5 # django-anvil-consortium-manager -django-dbbackup==4.1.0 +django-dbbackup==4.2.1 # via -r requirements/requirements.in django-environ==0.10.0 # via -r requirements/requirements.in From 1c66bc8ed570bcf605a2ed3d8be4c962888444fd Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 30 Aug 2024 09:12:46 -0700 Subject: [PATCH 123/125] Use sequences in factories for model unique fields --- gregor_django/gregor_anvil/tests/factories.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 2a1f64a0..5bb9b0ce 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -11,7 +11,7 @@ class ConsentGroupFactory(DjangoModelFactory): """A factory for the ConsentGroup model.""" - code = Faker("word") + code = Sequence(lambda x: "C{}".format(x)) consent = Faker("catch_phrase") data_use_limitations = Faker("paragraph", nb_sentences=10) @@ -23,7 +23,7 @@ class Meta: class ResearchCenterFactory(DjangoModelFactory): """A factory for the ResearchCenter model.""" - short_name = Faker("word") + short_name = Sequence(lambda x: "RC{}".format(x)) full_name = Faker("company") class Meta: @@ -62,7 +62,7 @@ class Meta: class PartnerGroupFactory(DjangoModelFactory): """A factory for the PartnerGroup model.""" - short_name = Faker("word") + short_name = Sequence(lambda x: "PG{}".format(x)) full_name = Faker("company") class Meta: From ef434ff895cd82891fca0c4111eff4a973bcf99c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:29:37 +0000 Subject: [PATCH 124/125] Bump mypy from 1.11.1 to 1.11.2 Bumps [mypy](https://github.com/python/mypy) from 1.11.1 to 1.11.2. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.11.1...v1.11.2) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 16e31a22..53e97717 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -100,7 +100,7 @@ matplotlib-inline==0.1.6 # via ipython mccabe==0.7.0 # via pylint -mypy==1.11.1 +mypy==1.11.2 # via -r requirements/dev-requirements.in mypy-extensions==1.0.0 # via mypy From 73aca923eebb1c6b48524549440af06c2916f2d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:30:08 +0000 Subject: [PATCH 125/125] Bump ruff from 0.6.1 to 0.6.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.1 to 0.6.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.6.1...0.6.3) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 16e31a22..3d690cbe 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -150,7 +150,7 @@ requests==2.32.3 # -c requirements/requirements.txt # -c requirements/test-requirements.txt # sphinx -ruff==0.6.1 +ruff==0.6.3 # via -r requirements/dev-requirements.in six==1.16.0 # via