diff --git a/primed/cdsa/adapters.py b/primed/cdsa/adapters.py index 78e6f169..58571ef2 100644 --- a/primed/cdsa/adapters.py +++ b/primed/cdsa/adapters.py @@ -1,13 +1,14 @@ from anvil_consortium_manager.adapters.workspace import BaseWorkspaceAdapter -from anvil_consortium_manager.forms import WorkspaceForm from anvil_consortium_manager.models import Workspace from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceUserTable +from primed.primed_anvil.adapters import WorkspaceAdminSharingAdapterMixin, WorkspaceAuthDomainAdapterMixin +from primed.primed_anvil.forms import WorkspaceAuthDomainDisabledForm from . import forms, models, tables -class CDSAWorkspaceAdapter(BaseWorkspaceAdapter): +class CDSAWorkspaceAdapter(WorkspaceAuthDomainAdapterMixin, WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): """Adapter for CDSAWorkspaces.""" type = "cdsa" @@ -15,7 +16,7 @@ class CDSAWorkspaceAdapter(BaseWorkspaceAdapter): description = "Workspaces containing data from the Consortium Data Sharing Agreement" list_table_class_staff_view = tables.CDSAWorkspaceStaffTable list_table_class_view = tables.CDSAWorkspaceUserTable - workspace_form_class = WorkspaceForm + workspace_form_class = WorkspaceAuthDomainDisabledForm workspace_data_model = models.CDSAWorkspace workspace_data_form_class = forms.CDSAWorkspaceForm workspace_detail_template_name = "cdsa/cdsaworkspace_detail.html" diff --git a/primed/cdsa/tests/test_views.py b/primed/cdsa/tests/test_views.py index 6015197d..2cb3efce 100644 --- a/primed/cdsa/tests/test_views.py +++ b/primed/cdsa/tests/test_views.py @@ -9,6 +9,7 @@ GroupGroupMembership, ManagedGroup, Workspace, + WorkspaceGroupSharing, ) from anvil_consortium_manager.tests.api_factories import ErrorResponseFactory from anvil_consortium_manager.tests.factories import ( @@ -10262,23 +10263,27 @@ def setUp(self): ) self.requester = UserFactory.create() self.workspace_type = "cdsa" + # Create the admins group. + ManagedGroupFactory.create(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) def get_url(self, *args): """Get the url for the view being tested.""" return reverse("anvil_consortium_manager:workspaces:new", args=args) - def test_creates_upload_workspace_without_duos(self): + def test_creates_workspace(self): """Posting valid data to the form creates a workspace data object when using a custom adapter.""" study = factories.StudyFactory.create() duo_permission = DataUsePermissionFactory.create() # Create an extra that won't be specified. DataUseModifierFactory.create() billing_project = BillingProjectFactory.create(name="test-billing-project") + # API response for workspace creation. url = self.api_client.rawls_entry_point + "/api/workspaces" json_data = { "namespace": "test-billing-project", "name": "test-workspace", "attributes": {}, + "authorizationDomain": [{"membersGroupName": "AUTH_test-workspace"}], } self.anvil_response_mock.add( responses.POST, @@ -10286,6 +10291,37 @@ def test_creates_upload_workspace_without_duos(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_test-workspace", + status=self.api_success_code, + ) + # API response for auth domain PRIMED_ADMINS membership. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + + "/api/groups/v1/AUTH_test-workspace/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", + status=204, + ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -10315,6 +10351,24 @@ def test_creates_upload_workspace_without_duos(self): self.assertEqual(new_workspace_data.data_use_permission, duo_permission) self.assertEqual(new_workspace_data.acknowledgments, "test acknowledgments") self.assertEqual(new_workspace_data.requested_by, self.requester) + # Check that auth domain exists. + self.assertEqual(new_workspace.authorization_domains.count(), 1) + auth_domain = new_workspace.authorization_domains.first() + self.assertEqual(auth_domain.name, "AUTH_test-workspace") + self.assertTrue(auth_domain.is_managed_by_app) + self.assertEqual(auth_domain.email, "AUTH_test-workspace@firecloud.org") + # Check that auth domain admin is correct. + membership = GroupGroupMembership.objects.get( + parent_group=auth_domain, child_group__name="TEST_PRIMED_CC_ADMINS" + ) + self.assertEqual(membership.role, membership.ADMIN) + # Check that workspace sharing is correct. + sharing = WorkspaceGroupSharing.objects.get( + workspace=new_workspace, + group__name="TEST_PRIMED_CC_ADMINS", + ) + self.assertEqual(sharing.access, sharing.OWNER) + self.assertEqual(sharing.can_compute, True) def test_creates_upload_workspace_with_duo_modifiers(self): """Posting valid data to the form creates a workspace data object when using a custom adapter.""" @@ -10325,11 +10379,13 @@ def test_creates_upload_workspace_with_duo_modifiers(self): # Create an extra that won't be specified. DataUseModifierFactory.create() billing_project = BillingProjectFactory.create(name="test-billing-project") + # API response for workspace creation. url = self.api_client.rawls_entry_point + "/api/workspaces" json_data = { "namespace": "test-billing-project", "name": "test-workspace", "attributes": {}, + "authorizationDomain": [{"membersGroupName": "AUTH_test-workspace"}], } self.anvil_response_mock.add( responses.POST, @@ -10337,6 +10393,37 @@ def test_creates_upload_workspace_with_duo_modifiers(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_test-workspace", + status=self.api_success_code, + ) + # API response for auth domain PRIMED_ADMINS membership. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + + "/api/groups/v1/AUTH_test-workspace/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", + status=204, + ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -10371,11 +10458,13 @@ def test_creates_upload_workspace_with_disease_term(self): data_use_permission = DataUsePermissionFactory.create(requires_disease_term=True) # Create an extra that won't be specified. billing_project = BillingProjectFactory.create(name="test-billing-project") + # API response for workspace creation. url = self.api_client.rawls_entry_point + "/api/workspaces" json_data = { "namespace": "test-billing-project", "name": "test-workspace", "attributes": {}, + "authorizationDomain": [{"membersGroupName": "AUTH_test-workspace"}], } self.anvil_response_mock.add( responses.POST, @@ -10383,6 +10472,37 @@ def test_creates_upload_workspace_with_disease_term(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_test-workspace", + status=self.api_success_code, + ) + # API response for auth domain PRIMED_ADMINS membership. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + + "/api/groups/v1/AUTH_test-workspace/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", + status=204, + ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), diff --git a/primed/collaborative_analysis/adapters.py b/primed/collaborative_analysis/adapters.py index f070d926..846c4e24 100644 --- a/primed/collaborative_analysis/adapters.py +++ b/primed/collaborative_analysis/adapters.py @@ -1,10 +1,16 @@ from anvil_consortium_manager.adapters.workspace import BaseWorkspaceAdapter -from anvil_consortium_manager.forms import WorkspaceForm + +from primed.primed_anvil.adapters import WorkspaceAdminSharingAdapterMixin, WorkspaceAuthDomainAdapterMixin +from primed.primed_anvil.forms import WorkspaceAuthDomainDisabledForm from . import forms, models, tables -class CollaborativeAnalysisWorkspaceAdapter(BaseWorkspaceAdapter): +class CollaborativeAnalysisWorkspaceAdapter( + WorkspaceAuthDomainAdapterMixin, + WorkspaceAdminSharingAdapterMixin, + BaseWorkspaceAdapter, +): """Adapter for CollaborativeAnalysisWorkspace.""" type = "collab_analysis" @@ -12,7 +18,7 @@ class CollaborativeAnalysisWorkspaceAdapter(BaseWorkspaceAdapter): description = "Workspaces used for collaborative analyses" list_table_class_staff_view = tables.CollaborativeAnalysisWorkspaceStaffTable list_table_class_view = tables.CollaborativeAnalysisWorkspaceUserTable - workspace_form_class = WorkspaceForm + workspace_form_class = WorkspaceAuthDomainDisabledForm workspace_data_model = models.CollaborativeAnalysisWorkspace workspace_data_form_class = forms.CollaborativeAnalysisWorkspaceForm workspace_detail_template_name = "collaborative_analysis/collaborativeanalysisworkspace_detail.html" diff --git a/primed/collaborative_analysis/tests/test_views.py b/primed/collaborative_analysis/tests/test_views.py index 36327a31..e057e984 100644 --- a/primed/collaborative_analysis/tests/test_views.py +++ b/primed/collaborative_analysis/tests/test_views.py @@ -8,6 +8,7 @@ GroupAccountMembership, GroupGroupMembership, Workspace, + WorkspaceGroupSharing, ) from anvil_consortium_manager.tests.api_factories import ErrorResponseFactory from anvil_consortium_manager.tests.factories import ( @@ -186,6 +187,8 @@ def setUp(self): self.custodian = UserFactory.create() self.source_workspace = dbGaPWorkspaceFactory.create().workspace self.analyst_group = ManagedGroupFactory.create() + # Create the admins group. + self.admins_group = ManagedGroupFactory.create(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) def get_url(self, *args): """Get the url for the view being tested.""" @@ -194,11 +197,25 @@ def get_url(self, *args): def test_creates_workspace(self): """Posting valid data to the form creates a workspace data object when using a custom adapter.""" billing_project = BillingProjectFactory.create(name="test-billing-project") + # url = self.api_client.rawls_entry_point + "/api/workspaces" + # json_data = { + # "namespace": "test-billing-project", + # "name": "test-workspace", + # "attributes": {}, + # } + # self.anvil_response_mock.add( + # responses.POST, + # url, + # status=self.api_success_code, + # match=[responses.matchers.json_params_matcher(json_data)], + # ) + # API response for workspace creation. url = self.api_client.rawls_entry_point + "/api/workspaces" json_data = { "namespace": "test-billing-project", "name": "test-workspace", "attributes": {}, + "authorizationDomain": [{"membersGroupName": "AUTH_test-workspace"}], } self.anvil_response_mock.add( responses.POST, @@ -206,6 +223,37 @@ def test_creates_workspace(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_test-workspace", + status=self.api_success_code, + ) + # API response for auth domain PRIMED_ADMINS membership. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + + "/api/groups/v1/AUTH_test-workspace/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", + status=204, + ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -230,6 +278,24 @@ def test_creates_workspace(self): self.assertEqual(models.CollaborativeAnalysisWorkspace.objects.count(), 1) new_workspace_data = models.CollaborativeAnalysisWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) + # Check that auth domain exists. + self.assertEqual(new_workspace.authorization_domains.count(), 1) + auth_domain = new_workspace.authorization_domains.first() + self.assertEqual(auth_domain.name, "AUTH_test-workspace") + self.assertTrue(auth_domain.is_managed_by_app) + self.assertEqual(auth_domain.email, "AUTH_test-workspace@firecloud.org") + # Check that auth domain admin is correct. + membership = GroupGroupMembership.objects.get( + parent_group=auth_domain, child_group__name="TEST_PRIMED_CC_ADMINS" + ) + self.assertEqual(membership.role, membership.ADMIN) + # Check that workspace sharing is correct. + sharing = WorkspaceGroupSharing.objects.get( + workspace=new_workspace, + group__name="TEST_PRIMED_CC_ADMINS", + ) + self.assertEqual(sharing.access, sharing.OWNER) + self.assertEqual(sharing.can_compute, True) class CollaborativeAnalysisWorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): diff --git a/primed/dbgap/adapters.py b/primed/dbgap/adapters.py index d1eaf728..ae54759d 100644 --- a/primed/dbgap/adapters.py +++ b/primed/dbgap/adapters.py @@ -1,13 +1,14 @@ from anvil_consortium_manager.adapters.workspace import BaseWorkspaceAdapter -from anvil_consortium_manager.forms import WorkspaceForm from anvil_consortium_manager.models import Workspace from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceUserTable +from primed.primed_anvil.adapters import WorkspaceAdminSharingAdapterMixin, WorkspaceAuthDomainAdapterMixin +from primed.primed_anvil.forms import WorkspaceAuthDomainDisabledForm from . import forms, models, tables -class dbGaPWorkspaceAdapter(BaseWorkspaceAdapter): +class dbGaPWorkspaceAdapter(WorkspaceAuthDomainAdapterMixin, WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): """Adapter for dbGaPWorkspaces.""" type = "dbgap" @@ -15,7 +16,7 @@ class dbGaPWorkspaceAdapter(BaseWorkspaceAdapter): description = "Workspaces containing data from released dbGaP accessions" list_table_class_staff_view = tables.dbGaPWorkspaceStaffTable list_table_class_view = tables.dbGaPWorkspaceUserTable - workspace_form_class = WorkspaceForm + workspace_form_class = WorkspaceAuthDomainDisabledForm workspace_data_model = models.dbGaPWorkspace workspace_data_form_class = forms.dbGaPWorkspaceForm workspace_detail_template_name = "dbgap/dbgapworkspace_detail.html" diff --git a/primed/dbgap/tests/test_views.py b/primed/dbgap/tests/test_views.py index dad3f2f3..79e06122 100644 --- a/primed/dbgap/tests/test_views.py +++ b/primed/dbgap/tests/test_views.py @@ -11,6 +11,7 @@ GroupGroupMembership, ManagedGroup, Workspace, + WorkspaceGroupSharing, ) from anvil_consortium_manager.tests.api_factories import ErrorResponseFactory from anvil_consortium_manager.tests.factories import ( @@ -947,6 +948,8 @@ def setUp(self): ) self.requester = UserFactory.create() self.workspace_type = "dbgap" + # Create the admins group. + self.admins_group = ManagedGroupFactory.create(name="TEST_PRIMED_CC_ADMINS") def get_url(self, *args): """Get the url for the view being tested.""" @@ -956,17 +959,19 @@ def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def test_creates_upload_workspace_without_duos(self): + def test_creates_workspace_without_duos(self): """Posting valid data to the form creates a workspace data object when using a custom adapter.""" dbgap_study_accession = factories.dbGaPStudyAccessionFactory.create() # Create an extra that won't be specified. DataUseModifierFactory.create() billing_project = BillingProjectFactory.create(name="test-billing-project") + # API response for workspace creation. url = self.api_client.rawls_entry_point + "/api/workspaces" json_data = { "namespace": "test-billing-project", "name": "test-workspace", "attributes": {}, + "authorizationDomain": [{"membersGroupName": "AUTH_test-workspace"}], } self.anvil_response_mock.add( responses.POST, @@ -974,6 +979,37 @@ def test_creates_upload_workspace_without_duos(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_test-workspace", + status=self.api_success_code, + ) + # API response for auth domain PRIMED_ADMINS membership. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + + "/api/groups/v1/AUTH_test-workspace/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", + status=204, + ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -1011,8 +1047,20 @@ def test_creates_upload_workspace_without_duos(self): self.assertEqual(new_workspace_data.data_use_limitations, "test limitations") self.assertEqual(new_workspace_data.acknowledgments, "test acknowledgments") self.assertEqual(new_workspace_data.requested_by, self.requester) + # Check that an auth domain was created. + self.assertEqual(new_workspace.authorization_domains.count(), 1) + self.assertEqual(new_workspace.authorization_domains.first().name, "AUTH_test-workspace") + # Check that the PRIMED_ADMINS group is an admin of the auth domain. + membership = GroupGroupMembership.objects.get( + parent_group=new_workspace.authorization_domains.first(), + child_group=self.admins_group, + ) + self.assertEqual(membership.role, membership.ADMIN) + # Check that the workspace was shared with the admins group. + sharing = WorkspaceGroupSharing.objects.get(workspace=new_workspace, group=self.admins_group) + self.assertEqual(sharing.access, sharing.OWNER) - def test_creates_upload_workspace_with_duos(self): + def test_creates_workspace_with_duos(self): """Posting valid data to the form creates a workspace data object when using a custom adapter.""" dbgap_study_accession = factories.dbGaPStudyAccessionFactory.create() data_use_permission = DataUsePermissionFactory.create() @@ -1021,11 +1069,13 @@ def test_creates_upload_workspace_with_duos(self): # Create an extra that won't be specified. DataUseModifierFactory.create() billing_project = BillingProjectFactory.create(name="test-billing-project") + # API response for workspace creation. url = self.api_client.rawls_entry_point + "/api/workspaces" json_data = { "namespace": "test-billing-project", "name": "test-workspace", "attributes": {}, + "authorizationDomain": [{"membersGroupName": "AUTH_test-workspace"}], } self.anvil_response_mock.add( responses.POST, @@ -1033,6 +1083,37 @@ def test_creates_upload_workspace_with_duos(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_test-workspace", + status=self.api_success_code, + ) + # API response for auth domain PRIMED_ADMINS membership. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + + "/api/groups/v1/AUTH_test-workspace/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", + status=204, + ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), diff --git a/primed/miscellaneous_workspaces/adapters.py b/primed/miscellaneous_workspaces/adapters.py index 42a2d72c..ebcefdd2 100644 --- a/primed/miscellaneous_workspaces/adapters.py +++ b/primed/miscellaneous_workspaces/adapters.py @@ -3,6 +3,7 @@ from anvil_consortium_manager.adapters.workspace import BaseWorkspaceAdapter from anvil_consortium_manager.forms import WorkspaceForm +from primed.primed_anvil.adapters import WorkspaceAdminSharingAdapterMixin from primed.primed_anvil.tables import ( DefaultWorkspaceStaffTable, DefaultWorkspaceUserTable, @@ -11,7 +12,7 @@ from . import forms, models, tables -class SimulatedDataWorkspaceAdapter(BaseWorkspaceAdapter): +class SimulatedDataWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): """Adapter for SimulatedDataWorkspaces.""" type = "simulated_data" @@ -25,7 +26,7 @@ class SimulatedDataWorkspaceAdapter(BaseWorkspaceAdapter): workspace_detail_template_name = "miscellaneous_workspaces/simulateddataworkspace_detail.html" -class ConsortiumDevelWorkspaceAdapter(BaseWorkspaceAdapter): +class ConsortiumDevelWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): """Adapter for ConsortiumDevelWorkspaces.""" type = "devel" @@ -39,7 +40,7 @@ class ConsortiumDevelWorkspaceAdapter(BaseWorkspaceAdapter): workspace_detail_template_name = "anvil_consortium_manager/workspace_detail.html" -class ResourceWorkspaceAdapter(BaseWorkspaceAdapter): +class ResourceWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): """Adapter for ResourceWorkspaces.""" type = "resource" @@ -53,7 +54,7 @@ class ResourceWorkspaceAdapter(BaseWorkspaceAdapter): workspace_detail_template_name = "anvil_consortium_manager/workspace_detail.html" -class TemplateWorkspaceAdapter(BaseWorkspaceAdapter): +class TemplateWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): """Adapter for TemplateWorkspaces.""" type = "template" @@ -67,7 +68,7 @@ class TemplateWorkspaceAdapter(BaseWorkspaceAdapter): workspace_detail_template_name = "anvil_consortium_manager/workspace_detail.html" -class OpenAccessWorkspaceAdapter(BaseWorkspaceAdapter): +class OpenAccessWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): """Adapter for TemplateWorkspaces.""" type = "open_access" @@ -81,7 +82,7 @@ class OpenAccessWorkspaceAdapter(BaseWorkspaceAdapter): workspace_detail_template_name = "miscellaneous_workspaces/openaccessworkspace_detail.html" -class DataPrepWorkspaceAdapter(BaseWorkspaceAdapter): +class DataPrepWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): """Adapter for DataPrepWorkspace.""" type = "data_prep" diff --git a/primed/miscellaneous_workspaces/tests/test_views.py b/primed/miscellaneous_workspaces/tests/test_views.py index ac351cea..34c53e4a 100644 --- a/primed/miscellaneous_workspaces/tests/test_views.py +++ b/primed/miscellaneous_workspaces/tests/test_views.py @@ -1,12 +1,14 @@ """Tests for views related to the `workspaces` app.""" import responses -from anvil_consortium_manager.models import AnVILProjectManagerAccess, Workspace +from anvil_consortium_manager.models import AnVILProjectManagerAccess, Workspace, WorkspaceGroupSharing from anvil_consortium_manager.tests.factories import ( BillingProjectFactory, + ManagedGroupFactory, WorkspaceFactory, ) from anvil_consortium_manager.tests.utils import AnVILAPIMockTestMixin +from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission from django.test import TestCase @@ -59,6 +61,7 @@ def setUp(self): ) self.requester = UserFactory.create() self.workspace_type = "simulated_data" + self.admins_group = ManagedGroupFactory.create(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) def get_url(self, *args): """Get the url for the view being tested.""" @@ -79,6 +82,24 @@ def test_creates_workspace(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -100,6 +121,9 @@ def test_creates_workspace(self): self.assertEqual(models.SimulatedDataWorkspace.objects.count(), 1) new_workspace_data = models.SimulatedDataWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) + # Check that the workspace was shared with the admins group. + sharing = WorkspaceGroupSharing.objects.get(workspace=new_workspace, group=self.admins_group) + self.assertEqual(sharing.access, sharing.OWNER) class SimulatedDataWorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): @@ -250,6 +274,7 @@ def setUp(self): ) self.requester = UserFactory.create() self.workspace_type = "devel" + self.admins_group = ManagedGroupFactory.create(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) def get_url(self, *args): """Get the url for the view being tested.""" @@ -270,6 +295,24 @@ def test_creates_workspace(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -291,6 +334,9 @@ def test_creates_workspace(self): self.assertEqual(models.ConsortiumDevelWorkspace.objects.count(), 1) new_workspace_data = models.ConsortiumDevelWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) + # Check that the workspace was shared with the admins group. + sharing = WorkspaceGroupSharing.objects.get(workspace=new_workspace, group=self.admins_group) + self.assertEqual(sharing.access, sharing.OWNER) class ConsortiumDevelWorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): @@ -441,6 +487,7 @@ def setUp(self): ) self.requester = UserFactory.create() self.workspace_type = "resource" + self.admins_group = ManagedGroupFactory.create(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) def get_url(self, *args): """Get the url for the view being tested.""" @@ -461,6 +508,24 @@ def test_creates_workspace(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -482,6 +547,9 @@ def test_creates_workspace(self): self.assertEqual(models.ResourceWorkspace.objects.count(), 1) new_workspace_data = models.ResourceWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) + # Check that the workspace was shared with the admins group. + sharing = WorkspaceGroupSharing.objects.get(workspace=new_workspace, group=self.admins_group) + self.assertEqual(sharing.access, sharing.OWNER) class ResourceWorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): @@ -631,6 +699,7 @@ def setUp(self): Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.workspace_type = "template" + self.admins_group = ManagedGroupFactory.create(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) def get_url(self, *args): """Get the url for the view being tested.""" @@ -651,6 +720,24 @@ def test_creates_workspace(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -673,6 +760,9 @@ def test_creates_workspace(self): new_workspace_data = models.TemplateWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) self.assertEqual(new_workspace_data.intended_usage, "Test usage") + # Check that the workspace was shared with the admins group. + sharing = WorkspaceGroupSharing.objects.get(workspace=new_workspace, group=self.admins_group) + self.assertEqual(sharing.access, sharing.OWNER) class TemplateWorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): @@ -828,6 +918,7 @@ def setUp(self): self.workspace_type = "open_access" self.requester = UserFactory.create() self.study = StudyFactory.create() + self.admins_group = ManagedGroupFactory.create(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) def get_url(self, *args): """Get the url for the view being tested.""" @@ -848,6 +939,24 @@ def test_creates_workspace(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -875,6 +984,9 @@ def test_creates_workspace(self): self.assertEqual(new_workspace_data.studies.count(), 1) self.assertIn(self.study, new_workspace_data.studies.all()) self.assertEqual(new_workspace_data.data_source, "test source") + # Check that the workspace was shared with the admins group. + sharing = WorkspaceGroupSharing.objects.get(workspace=new_workspace, group=self.admins_group) + self.assertEqual(sharing.access, sharing.OWNER) class OpenAccessWorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): @@ -1051,6 +1163,7 @@ def setUp(self): self.requester = UserFactory.create() self.target_workspace = WorkspaceFactory.create() self.workspace_type = "data_prep" + self.admins_group = ManagedGroupFactory.create(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) def get_url(self, *args): """Get the url for the view being tested.""" @@ -1071,6 +1184,24 @@ def test_creates_workspace(self): status=self.api_success_code, match=[responses.matchers.json_params_matcher(json_data)], ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + + "/api/workspaces/test-billing-project/test-workspace/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Make the post request self.client.force_login(self.user) response = self.client.post( self.get_url(self.workspace_type), @@ -1093,6 +1224,9 @@ def test_creates_workspace(self): self.assertEqual(models.DataPrepWorkspace.objects.count(), 1) new_workspace_data = models.DataPrepWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) + # Check that the workspace was shared with the admins group. + sharing = WorkspaceGroupSharing.objects.get(workspace=new_workspace, group=self.admins_group) + self.assertEqual(sharing.access, sharing.OWNER) class DataPrepWorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): diff --git a/primed/primed_anvil/adapters.py b/primed/primed_anvil/adapters.py index f62a4593..547c0ea4 100644 --- a/primed/primed_anvil/adapters.py +++ b/primed/primed_anvil/adapters.py @@ -1,4 +1,10 @@ from anvil_consortium_manager.adapters.account import BaseAccountAdapter +from anvil_consortium_manager.models import ( + GroupGroupMembership, + ManagedGroup, + WorkspaceGroupSharing, +) +from django.conf import settings from django.db.models import Q from .filters import AccountListFilter @@ -24,3 +30,50 @@ def get_autocomplete_label(self, account): else: name = "---" return "{} ({})".format(name, account.email) + + +class WorkspaceAuthDomainAdapterMixin: + """Helper class to add auth domains to workspaces.""" + + def before_anvil_create(self, workspace): + """Add authorization domain to workspace.""" + # Create the auth domain for the workspace. + super().before_anvil_create(workspace) + auth_domain_name = "AUTH_" + workspace.name + auth_domain = ManagedGroup.objects.create( + name=auth_domain_name, + is_managed_by_app=True, + email=auth_domain_name + "@firecloud.org", + ) + workspace.authorization_domains.add(auth_domain) + auth_domain.anvil_create() + # Add the ADMINs group as an admin of the auth domain. + try: + admins_group = ManagedGroup.objects.get(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) + except ManagedGroup.DoesNotExist: + return + membership = GroupGroupMembership.objects.create( + parent_group=auth_domain, + child_group=admins_group, + role=GroupGroupMembership.ADMIN, + ) + membership.anvil_create() + + +class WorkspaceAdminSharingAdapterMixin: + """Helper class to share workspaces with the PRIMED_CC_ADMINs group.""" + + def after_anvil_create(self, workspace): + super().after_anvil_create(workspace) + # Share the workspace with the ADMINs group as an owner. + try: + admins_group = ManagedGroup.objects.get(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) + except ManagedGroup.DoesNotExist: + return + sharing = WorkspaceGroupSharing.objects.create( + workspace=workspace, + group=admins_group, + access=WorkspaceGroupSharing.OWNER, + can_compute=True, + ) + sharing.anvil_create_or_update() diff --git a/primed/primed_anvil/forms.py b/primed/primed_anvil/forms.py index d7163fe0..91e1dfba 100644 --- a/primed/primed_anvil/forms.py +++ b/primed/primed_anvil/forms.py @@ -1,3 +1,4 @@ +from anvil_consortium_manager.forms import WorkspaceForm from django import forms @@ -5,3 +6,14 @@ class CustomDateInput(forms.widgets.DateInput): """Form widget to select a date with a calendar picker.""" input_type = "date" + + +class WorkspaceAuthDomainDisabledForm(WorkspaceForm): + """Form for creating a workspace with the authorization domains field disabled.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["authorization_domains"].disabled = True + self.fields["authorization_domains"].help_text = ( + "An authorization domain will be automatically created " "using the name of the workspace." + ) diff --git a/primed/primed_anvil/tests/test_adapters.py b/primed/primed_anvil/tests/test_adapters.py index 515b4b10..55dffb8b 100644 --- a/primed/primed_anvil/tests/test_adapters.py +++ b/primed/primed_anvil/tests/test_adapters.py @@ -1,6 +1,9 @@ -from anvil_consortium_manager.models import Account -from anvil_consortium_manager.tests.factories import AccountFactory -from django.test import TestCase +import responses +from anvil_consortium_manager.adapters.default import DefaultWorkspaceAdapter +from anvil_consortium_manager.models import Account, GroupGroupMembership, WorkspaceGroupSharing +from anvil_consortium_manager.tests.factories import AccountFactory, ManagedGroupFactory, WorkspaceFactory +from anvil_consortium_manager.tests.utils import AnVILAPIMockTestMixin +from django.test import TestCase, override_settings from primed.users.tests.factories import UserFactory @@ -57,3 +60,172 @@ def test_autocomplete_queryset_no_linked_user(self): self.assertEqual(len(queryset), 1) self.assertIn(account_1, queryset) self.assertNotIn(account_2, queryset) + + +class WorkspaceAuthDomainAdapterMixinTest(AnVILAPIMockTestMixin, TestCase): + def setUp(self): + super().setUp() + + class TestAdapter(adapters.WorkspaceAuthDomainAdapterMixin, DefaultWorkspaceAdapter): + pass + + self.adapter = TestAdapter() + + def test_before_anvil_create(self): + admins_group = ManagedGroupFactory.create(name="TEST_PRIMED_CC_ADMINS") + workspace = WorkspaceFactory.create(name="foo", workspace_type=self.adapter.get_type()) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_foo", + status=201, + ) + # API response for auth domain PRIMED_ADMINS membership. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_foo/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", + status=204, + ) + # Run the adapter method. + self.adapter.before_anvil_create(workspace) + self.assertEqual(workspace.authorization_domains.count(), 1) + auth_domain = workspace.authorization_domains.first() + self.assertEqual(auth_domain.name, "AUTH_foo") + self.assertTrue(auth_domain.is_managed_by_app) + self.assertEqual(auth_domain.email, "AUTH_foo@firecloud.org") + # Check for GroupGroupMembership. + self.assertEqual(GroupGroupMembership.objects.count(), 1) + membership = GroupGroupMembership.objects.first() + self.assertEqual(membership.parent_group, auth_domain) + self.assertEqual(membership.child_group, admins_group) + + @override_settings(ANVIL_CC_ADMINS_GROUP_NAME="foobar") + def test_before_anvil_create_different_cc_admins_name(self): + admins_group = ManagedGroupFactory.create(name="foobar") + # Create a Workspace instead of CDSAWorkspace to skip factory auth domain behavior. + workspace = WorkspaceFactory.create(name="foo", workspace_type=self.adapter.get_type()) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_foo", + status=201, + ) + # API response for auth domain PRIMED_ADMINS membership. + self.anvil_response_mock.add( + responses.PUT, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_foo/admin/foobar@firecloud.org", + status=204, + ) + # Run the adapter method. + self.adapter.before_anvil_create(workspace) + self.assertEqual(workspace.authorization_domains.count(), 1) + auth_domain = workspace.authorization_domains.first() + self.assertEqual(auth_domain.name, "AUTH_foo") + self.assertTrue(auth_domain.is_managed_by_app) + self.assertEqual(auth_domain.email, "AUTH_foo@firecloud.org") + # Check for GroupGroupMembership. + self.assertEqual(GroupGroupMembership.objects.count(), 1) + membership = GroupGroupMembership.objects.first() + self.assertEqual(membership.parent_group, auth_domain) + self.assertEqual(membership.child_group, admins_group) + + def test_before_anvil_create_admins_group_does_not_exist(self): + """If the admins group does not exist, the workspace is not shared.""" + workspace = WorkspaceFactory.create(name="foo", workspace_type=self.adapter.get_type()) + # API response for auth domain ManagedGroup creation. + self.anvil_response_mock.add( + responses.POST, + self.api_client.sam_entry_point + "/api/groups/v1/AUTH_foo", + status=201, + ) + # Run the adapter method. + self.adapter.before_anvil_create(workspace) + self.assertEqual(workspace.authorization_domains.count(), 1) + auth_domain = workspace.authorization_domains.first() + self.assertEqual(auth_domain.name, "AUTH_foo") + self.assertTrue(auth_domain.is_managed_by_app) + self.assertEqual(auth_domain.email, "AUTH_foo@firecloud.org") + # No GroupGroupMembership objects were created. + self.assertEqual(GroupGroupMembership.objects.count(), 0) + + +class WorkspaceAdminSharingAdapterMixin(AnVILAPIMockTestMixin, TestCase): + def setUp(self): + super().setUp() + + class TestAdapter(adapters.WorkspaceAdminSharingAdapterMixin, DefaultWorkspaceAdapter): + pass + + self.adapter = TestAdapter() + + def test_after_anvil_create(self): + admins_group = ManagedGroupFactory.create(name="TEST_PRIMED_CC_ADMINS") + workspace = WorkspaceFactory.create( + billing_project__name="bar", name="foo", workspace_type=self.adapter.get_type() + ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "TEST_PRIMED_CC_ADMINS@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/bar/foo/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Run the adapter method. + self.adapter.after_anvil_create(workspace) + # Check for WorkspaceGroupSharing. + self.assertEqual(WorkspaceGroupSharing.objects.count(), 1) + sharing = WorkspaceGroupSharing.objects.first() + self.assertEqual(sharing.workspace, workspace) + self.assertEqual(sharing.group, admins_group) + self.assertEqual(sharing.access, WorkspaceGroupSharing.OWNER) + self.assertTrue(sharing.can_compute) + + @override_settings(ANVIL_CC_ADMINS_GROUP_NAME="foobar") + def test_after_anvil_create_different_admins_group(self): + admins_group = ManagedGroupFactory.create(name="foobar") + workspace = WorkspaceFactory.create( + billing_project__name="bar", name="foo", workspace_type=self.adapter.get_type() + ) + # API response for PRIMED_ADMINS workspace owner. + acls = [ + { + "email": "foobar@firecloud.org", + "accessLevel": "OWNER", + "canShare": False, + "canCompute": True, + } + ] + self.anvil_response_mock.add( + responses.PATCH, + self.api_client.rawls_entry_point + "/api/workspaces/bar/foo/acl?inviteUsersNotFound=false", + status=200, + match=[responses.matchers.json_params_matcher(acls)], + json={"invitesSent": {}, "usersNotFound": {}, "usersUpdated": acls}, + ) + # Run the adapter method. + self.adapter.after_anvil_create(workspace) + # Check for WorkspaceGroupSharing. + self.assertEqual(WorkspaceGroupSharing.objects.count(), 1) + sharing = WorkspaceGroupSharing.objects.first() + self.assertEqual(sharing.workspace, workspace) + self.assertEqual(sharing.group, admins_group) + self.assertEqual(sharing.access, WorkspaceGroupSharing.OWNER) + self.assertTrue(sharing.can_compute) + + def test_after_anvil_create_no_admins_group(self): + workspace = WorkspaceFactory.create( + billing_project__name="bar", name="foo", workspace_type=self.adapter.get_type() + ) + # Run the adapter method. + self.adapter.after_anvil_create(workspace) + # No WorkspaceGroupSharing objects were created. + self.assertEqual(WorkspaceGroupSharing.objects.count(), 0) diff --git a/primed/templates/users/user_detail.html b/primed/templates/users/user_detail.html index 0261598e..75163ead 100644 --- a/primed/templates/users/user_detail.html +++ b/primed/templates/users/user_detail.html @@ -155,5 +155,4 @@

{% if object == request.user %}My{% el -

{{ signed_agreements }}

{% endblock content %} diff --git a/requirements/requirements.in b/requirements/requirements.in index 5775f119..5f17a1f3 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -33,7 +33,7 @@ django-dbbackup # https://github.com/jazzband/django-dbbackup django-extensions # https://github.com/django-extensions/django-extensions # anvil_consortium_manager -django-anvil-consortium-manager @ git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.23 +django-anvil-consortium-manager @ git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.24 # Simple history - model history tracking django-simple-history diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 57feb16a..028705a0 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -62,7 +62,7 @@ django==4.2.13 # django-tables2 django-allauth==0.54.0 # via -r requirements/requirements.in -django-anvil-consortium-manager @ git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.23 +django-anvil-consortium-manager @ git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.24 # via -r requirements/requirements.in django-autocomplete-light==3.11.0 # via django-anvil-consortium-manager