diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 17696141..d0a36568 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,12 @@ Added - App setting ``user_modifiable`` validation (#1536) - ``AppSettingAPI.get_all_by_scope()`` helper (#1534) - ``removeroles`` management command (#1391, #1541) + - Site read only mode (#24) + - ``site_read_only`` site app setting (#24) + - ``is_site_writable()`` rule predicate (#24) + - ``PermissionTestMixin.set_site_read_only()`` helper (#24) + - ``PROJECTROLES_READ_ONLY_MSG`` setting (#24) + - ``SiteReadOnlySettingAjaxView`` Ajax view (#24) Changed ------- @@ -33,6 +39,10 @@ Changed - Deprecate ``AppSettingAPI.get_all()`` (#1534) - Allow no role for old owner in ``RoleAssignmentOwnerTransferMixin`` (#836, #1391) - Allow no role for old owner in ``perform_owner_transfer()`` (#836, #1391) +- **Tokens** + - Update UI for site read-only mode (#24) +- **Userprofile** + - Update UI for site read-only mode (#24) Removed ------- diff --git a/bgjobs/rules.py b/bgjobs/rules.py index 908d33f2..1f12f0a8 100644 --- a/bgjobs/rules.py +++ b/bgjobs/rules.py @@ -41,24 +41,31 @@ # Allow creating background jobs rules.add_perm( 'bgjobs.create_bgjob', - pr_rules.is_project_owner - | pr_rules.is_project_delegate - | pr_rules.is_project_contributor, + ( + pr_rules.is_project_owner + | pr_rules.is_project_delegate + | pr_rules.is_project_contributor + ) + & pr_rules.is_site_writable, ) # Allow modifying or deleting the user's background jobs rules.add_perm( 'bgjobs.update_bgjob_own', - pr_rules.is_project_owner - | pr_rules.is_project_delegate - | pr_rules.is_project_contributor - | pr_rules.is_project_guest, + ( + pr_rules.is_project_owner + | pr_rules.is_project_delegate + | pr_rules.is_project_contributor + | pr_rules.is_project_guest + ) + & pr_rules.is_site_writable, ) # Allow modifying or deleting all background jobs rules.add_perm( 'bgjobs.update_bgjob_all', - pr_rules.is_project_owner | pr_rules.is_project_delegate, + (pr_rules.is_project_owner | pr_rules.is_project_delegate) + & pr_rules.is_site_writable, ) # Allow viewing site-global background jobs (not project-specific). diff --git a/config/settings/base.py b/config/settings/base.py index 1708ecc9..e5ef8977 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -626,15 +626,17 @@ def set_logging(level=None): ) # Optional projectroles settings -# Sidebar icon size. Minimum=18, maximum=42. +# Sidebar icon size (must be between 18-42) PROJECTROLES_SIDEBAR_ICON_SIZE = env.int('PROJECTROLES_SIDEBAR_ICON_SIZE', 36) # PROJECTROLES_SECRET_LENGTH = 32 # PROJECTROLES_HELP_HIGHLIGHT_DAYS = 7 # PROJECTROLES_SEARCH_PAGINATION = 5 -# Support for viewing the site in "kiosk mode" (under work, experimental) +# Support for viewing the site in "kiosk mode" (experimental) # PROJECTROLES_KIOSK_MODE = env.bool('PROJECTROLES_KIOSK_MODE', False) # Scroll project navigation with page content if set False # PROJECTROLES_BREADCRUMB_STICKY = True +# Custom message to be displayed if site read-only mode is enabled +PROJECTROLES_READ_ONLY_MSG = env.str('PROJECTROLES_READ_ONLY_MSG', None) # Hide project apps from the UI (sidebar, dropdown menus and project details) PROJECTROLES_HIDE_PROJECT_APPS = env.list( diff --git a/docs/source/app_projectroles_api_django.rst b/docs/source/app_projectroles_api_django.rst index bf8949a5..ec431f3f 100644 --- a/docs/source/app_projectroles_api_django.rst +++ b/docs/source/app_projectroles_api_django.rst @@ -106,6 +106,8 @@ General utility functions are stored in ``utils.py``. :members: +.. _app_projectroles_api_django_ajax_common: + Common Use Ajax Views ===================== @@ -113,11 +115,16 @@ Ajax views intended to be used in a SODAR Core based site are described here. .. currentmodule:: projectroles.views_ajax +.. autoclass:: CurrentUserRetrieveAjaxView + +.. autoclass:: SiteReadOnlySettingAjaxView + .. autoclass:: SidebarContentAjaxView .. autoclass:: UserDropdownContentAjaxView + .. _app_projectroles_api_django_rest: Base REST API View Classes diff --git a/docs/source/app_projectroles_usage.rst b/docs/source/app_projectroles_usage.rst index 8c7bc2f2..fdeaefbe 100644 --- a/docs/source/app_projectroles_usage.rst +++ b/docs/source/app_projectroles_usage.rst @@ -569,6 +569,5 @@ name and/or description. REST API ======== -Several SODAR Core functionalities are also available via a HTTP REST API -starting in version 0.8. See :ref:`app_projectroles_api_rest` for instructions -on REST API usage. +Many SODAR Core features are also available via a REST API. See +:ref:`app_projectroles_api_rest` for instructions on REST API usage. diff --git a/docs/source/dev_project_app.rst b/docs/source/dev_project_app.rst index df71dc87..4e1ad6e7 100644 --- a/docs/source/dev_project_app.rst +++ b/docs/source/dev_project_app.rst @@ -165,9 +165,18 @@ app if needed. .. hint:: For permissions dealing with modifying data, you are strongly recommend to - use the ``can_modify_project_data`` predicate. For more, see + use the ``can_modify_project_data`` predicate. For more information, see :ref:`dev_project_app_archiving`. +.. hint:: + + To support the site read-only mode introduced in SODAR Core v1.1, the rules + for your app's views need to be implemented accordingly. A check for the + read-only mode is contained in the ``can_modify_project_data()`` predicate. + If your view already uses that predicate, no further steps are necessary. + For site views, ``is_site_writable`` should be used. For more information, + see :ref:`dev_resources_read_only`. + ProjectAppPlugin ================ diff --git a/docs/source/dev_resource.rst b/docs/source/dev_resource.rst index 5adc6f6a..24ac9730 100644 --- a/docs/source/dev_resource.rst +++ b/docs/source/dev_resource.rst @@ -469,6 +469,85 @@ when creating multi-plugin apps: This, again, ensures apps are correctly detected and highlighted in the UI. +.. _dev_resources_read_only: + +Site Read-Only Mode +=================== + +A superuser can temporarily set the site into read-only mode. When the mode is +enabled, all data on the site is only accessible for reading. No project or user +data should be modifiable, except for superusers who still have full access. + +SODAR Core apps enforce this mode by prohibiting access to views and/or UI +elements which enable the user to modify data. Apps developed for a SODAR Core +based site must implement this within their rule and UI logic. + +If your data modifying view is in a project app and uses the +``can_modify_project_data()`` rule predicate, checks for view access are already +performed for that view in the permission checks. Example of this in a +``rules.py`` file: + +.. code-block:: python + + import rules + from projectroles import rules as pr_rules + + rules.add_perm( + 'your_project_app.update_data', + pr_rules.can_modify_project_data + & ( + pr_rules.is_project_owner + | pr_rules.is_project_delegate + | pr_rules.is_project_contributor + ), + ) + +For site views, you can use the ``is_site_writable()`` predicate. Example: + +.. code-block:: python + + import rules + from projectroles import rules as pr_rules + + rules.add_perm( + 'your_site_app.update_data', + rules.is_authenticated & pr_rules.is_site_writable, + ) + +To check for the mode in your Python code, you should use the app settings API +as follows: + +.. code-block:: python + + from projectroles.app_settings import AppSettingAPI + app_settings = AppSettingAPI() + + if app_settings.get('projectroles', 'site_read_only'): + pass # Add logic for read-only mode here + +In templates, the same can be done using the ``get_app_setting()`` template tag. +Example: + +.. code-block:: django + + {% load projectroles_common_tags %} + {% get_app_setting 'projectroles' 'site_read_only' as site_read_only %} + {% if site_read_only %} + {# ... #} + {% endif %} + +If you need to check the site read-only status in client-side apps, you can +query the ``SiteReadOnlySettingAjaxView`` Ajax view. See +:ref:`app_projectroles_api_django_ajax_common` for more information. + +.. note:: + + It is assumed that in read-only mode, superusers are still able to access + data modifying views and operations. The rule settings also allow this. + Actions within management commands should thus also be allowed in read-only + mode. + + Management Command Logger ========================= diff --git a/docs/source/major_changes.rst b/docs/source/major_changes.rst index bbdf0ec3..328c5f31 100644 --- a/docs/source/major_changes.rst +++ b/docs/source/major_changes.rst @@ -16,6 +16,7 @@ v1.1.0 (WIP) Release Highlights ================== +- Add site read-only mode - Add removeroles management command - Add app setting type constants - Add app setting definition as objects @@ -26,6 +27,14 @@ Release Highlights Breaking Changes ================ +Site Read-Only Mode +------------------- + +This release adds the site-wide read-only mode, which is intended to temporarily +prohibit modifying all data on the site. Rules, logic and/or UI of your apps' +views may have to be changed to support this functionality. For more +information, see :ref:`dev_resources_read_only`. + AppSettingAPI Definition Getter Return Data ------------------------------------------- diff --git a/filesfolders/tests/test_permissions.py b/filesfolders/tests/test_permissions.py index cabb47ed..7b632ca6 100644 --- a/filesfolders/tests/test_permissions.py +++ b/filesfolders/tests/test_permissions.py @@ -77,7 +77,7 @@ def make_test_link(self): # Test Cases ------------------------------------------------------------------- -class TestProjectFileViewPermissions( +class TestProjectFileView( FilesfoldersPermissionTestMixin, ProjectPermissionTestBase ): """Tests for ProjectFileView permissions""" @@ -87,10 +87,7 @@ def setUp(self): self.url = reverse( 'filesfolders:list', kwargs={'project': self.project.sodar_uuid} ) - - def test_get(self): - """Test ProjectFileView GET""" - good_users = [ + self.good_users = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -101,9 +98,16 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.bad_users = [ + self.user_finder_cat, + self.user_no_roles, + self.anonymous, + ] + + def test_get(self): + """Test ProjectFileView GET""" + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) # Test public project self.project.set_public() self.assert_response(self.url, self.user_no_roles, 200) @@ -118,24 +122,18 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) self.project.set_public() self.assert_response(self.url, self.user_no_roles, 200) self.assert_response(self.url, self.anonymous, 302) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) + class TestFolderCreateView( FilesfoldersPermissionTestMixin, ProjectPermissionTestBase @@ -181,24 +179,16 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) def test_get_category(self): """Test GET with folder under category (should fail)""" @@ -206,21 +196,7 @@ def test_get_category(self): 'filesfolders:folder_create', kwargs={'project': self.category.sodar_uuid}, ) - bad_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(url, bad_users, 302) + self.assert_response(url, self.all_users, 302) class TestFolderUpdateView( @@ -268,24 +244,16 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) class TestFolderDeleteView( @@ -333,24 +301,16 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) class TestFileCreateView( @@ -394,27 +354,19 @@ def test_get_anon(self): self.project.set_public() self.assert_response(self.url, self.anonymous, 302) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) + def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) def test_get_category(self): """Test GET under category (should fail)""" @@ -422,21 +374,7 @@ def test_get_category(self): 'filesfolders:file_create', kwargs={'project': self.category.sodar_uuid}, ) - bad_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(url, bad_users, 302) + self.assert_response(url, self.all_users, 302) class TestFileUpdateView( @@ -483,24 +421,16 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) class TestFileDeleteView( @@ -547,24 +477,16 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) class TestFilePublicLinkView( @@ -579,10 +501,7 @@ def setUp(self): 'filesfolders:file_public_link', kwargs={'file': file.sodar_uuid}, ) - - def test_get(self): - """Test FilePublicLinkView GET""" - good_users = [ + self.good_users = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -591,17 +510,20 @@ def test_get(self): self.user_delegate, self.user_contributor, ] - bad_users = [ + self.bad_users = [ self.user_guest_cat, self.user_finder_cat, self.user_guest, self.user_no_roles, self.anonymous, ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + + def test_get(self): + """Test FilePublicLinkView GET""" + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.bad_users, 302) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_get_anon(self): @@ -610,28 +532,18 @@ def test_get_anon(self): self.assert_response(self.url, self.anonymous, 302) def test_get_archive(self): - """Test get with archived project""" + """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - ] - bad_users = [ - self.user_guest_cat, - self.user_finder_cat, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.bad_users, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) class TestFileServeView( @@ -646,10 +558,7 @@ def setUp(self): 'filesfolders:file_serve', kwargs={'file': file.sodar_uuid, 'file_name': file.name}, ) - - def test_get(self): - """Test FileServeView GET""" - good_users = [ + self.good_users = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -660,9 +569,16 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.bad_users = [ + self.user_finder_cat, + self.user_no_roles, + self.anonymous, + ] + + def test_get(self): + """Test FileServeView GET""" + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) self.project.set_public() self.assert_response(self.url, self.user_no_roles, 200) self.assert_response(self.url, self.anonymous, 302) @@ -676,24 +592,18 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles, self.anonymous] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) self.project.set_public() self.assert_response(self.url, self.user_no_roles, 200) self.assert_response(self.url, self.anonymous, 302) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.good_users, 200) + self.assert_response(self.url, self.bad_users, 302) + class TestFileServePublicView( FilesfoldersPermissionTestMixin, ProjectPermissionTestBase @@ -710,21 +620,7 @@ def setUp(self): def test_get(self): """Test FileServePublicView GET""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, self.all_users, 200) self.project.set_public() self.assert_response( self.url, [self.user_no_roles, self.anonymous], 200 @@ -737,23 +633,9 @@ def test_get_anon(self): self.assert_response(self.url, self.anonymous, 200) def test_get_archived(self): - """Test FileServePublicView GET with archived project""" + """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) + self.assert_response(self.url, self.all_users, 200) self.project.set_public() self.assert_response( self.url, [self.user_no_roles, self.anonymous], 200 @@ -764,21 +646,12 @@ def test_get_disabled(self): app_settings.set( APP_NAME, 'allow_public_links', False, project=self.project ) - bad_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, bad_users, 400) + self.assert_response(self.url, self.all_users, 400) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.all_users, 200) class TestHyperLinkCreateView( @@ -825,24 +698,16 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) def test_get_category(self): """Test GET under category (should fail)""" @@ -850,21 +715,7 @@ def test_get_category(self): 'filesfolders:hyperlink_create', kwargs={'project': self.category.sodar_uuid}, ) - bad_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(url, bad_users, 302) + self.assert_response(url, self.all_users, 302) class TestHyperLinkUpdateView( @@ -912,24 +763,16 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) class TestHyperLinkDeleteView( @@ -977,24 +820,16 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - self.anonymous, - ] - self.assert_response(self.url, good_users, 200) - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) self.project.set_public() - self.assert_response(self.url, bad_users, 302) + self.assert_response(self.url, self.non_superusers, 302) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response(self.url, self.superuser, 200) + self.assert_response(self.url, self.non_superusers, 302) class TestBatchEditView( @@ -1053,26 +888,43 @@ def test_post_anon(self): def test_post_archive(self): """Test POST with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response( - self.url, good_users, 200, method='POST', data=self.post_data + self.url, self.superuser, 200, method='POST', data=self.post_data ) self.assert_response( - self.url, bad_users, 302, method='POST', data=self.post_data + self.url, + self.non_superusers, + 302, + method='POST', + data=self.post_data, ) self.project.set_public() self.assert_response( - self.url, bad_users, 302, method='POST', data=self.post_data + self.url, + self.non_superusers, + 302, + method='POST', + data=self.post_data, + ) + + def test_post_read_only(self): + """Test POST with site read-only mode""" + self.set_site_read_only() + self.assert_response( + self.url, self.superuser, 200, method='POST', data=self.post_data + ) + self.assert_response( + self.url, + self.non_superusers, + 302, + method='POST', + data=self.post_data, + ) + self.project.set_public() + self.assert_response( + self.url, + self.non_superusers, + 302, + method='POST', + data=self.post_data, ) diff --git a/filesfolders/tests/test_permissions_api.py b/filesfolders/tests/test_permissions_api.py index 5d0f4098..5a61d586 100644 --- a/filesfolders/tests/test_permissions_api.py +++ b/filesfolders/tests/test_permissions_api.py @@ -47,10 +47,7 @@ def setUp(self): 'flag': 'IMPORTANT', 'description': 'Description', } - - def test_get(self): - """Test FolderListCreateAPIView GET""" - good_users = [ + self.good_users_get = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -61,11 +58,14 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200) - self.assert_response_api(self.url, bad_users, 403) + self.bad_users_get = [self.user_finder_cat, self.user_no_roles] + + def test_get(self): + """Test FolderListCreateAPIView GET""" + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) @@ -78,25 +78,20 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200) - self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) + self.assert_response_api(self.url, self.anonymous, 401) + def test_post(self): """Test FolderListCreateAPIView POST""" good_users = [ @@ -151,31 +146,22 @@ def test_post_anon(self): def test_post_archive(self): """Test POST with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 201, method='POST', data=self.post_data + self.url, self.superuser, 201, method='POST', data=self.post_data ) self.assert_response_api( - self.url, bad_users, 403, method='POST', data=self.post_data + self.url, + self.auth_non_superusers, + 403, + method='POST', + data=self.post_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='POST', data=self.post_data ) self.assert_response_api( self.url, - good_users, + self.superuser, 201, method='POST', data=self.post_data, @@ -190,6 +176,23 @@ def test_post_archive(self): data=self.post_data, ) + def test_post_read_only(self): + """Test POST with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, self.superuser, 201, method='POST', data=self.post_data + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='POST', + data=self.post_data, + ) + self.assert_response_api( + self.url, self.anonymous, 401, method='POST', data=self.post_data + ) + class TestFolderRetrieveUpdateDestroyAPIView(FilesfoldersAPIPermissionTestBase): """Tests for FolderRetrieveUpdateDestroyAPIView permissions""" @@ -213,10 +216,7 @@ def setUp(self): 'description': 'UPDATED Description', } self.patch_data = {'name': 'UPDATED Folder'} - - def test_get(self): - """Test FolderRetrieveUpdateDestroyAPIView GET""" - good_users = [ + self.good_users_get = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -227,11 +227,32 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200, method='GET') - self.assert_response_api(self.url, bad_users, 403) + self.bad_users_get = [self.user_finder_cat, self.user_no_roles] + self.good_users_update = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_owner, + self.user_delegate, + ] + self.bad_users_update = [ + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_contributor, + self.user_guest, + self.user_no_roles, + ] + + def test_get(self): + """Test FolderRetrieveUpdateDestroyAPIView GET""" + + self.assert_response_api( + self.url, self.good_users_get, 200, method='GET' + ) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) @@ -244,53 +265,45 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200, method='GET') - self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api( + self.url, self.good_users_get, 200, method='GET' + ) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, self.good_users_get, 200, method='GET' + ) + self.assert_response_api(self.url, self.bad_users_get, 403) + self.assert_response_api(self.url, self.anonymous, 401) + def test_put(self): """Test FolderRetrieveUpdateDestroyAPIView PUT""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 200, method='PUT', data=self.put_data + self.url, + self.good_users_update, + 200, + method='PUT', + data=self.put_data, ) self.assert_response_api( - self.url, bad_users, 403, method='PUT', data=self.put_data + self.url, + self.bad_users_update, + 403, + method='PUT', + data=self.put_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='PUT', data=self.put_data ) self.assert_response_api( self.url, - good_users, + self.good_users_update, 200, method='PUT', data=self.put_data, @@ -303,33 +316,24 @@ def test_put(self): @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_put_anon(self): - """Test permissions for folder updating with anonymous access""" + """Test PUT with anonymous access""" self.project.set_public() self.assert_response_api( self.url, self.anonymous, 401, method='PUT', data=self.put_data ) def test_put_archive(self): - """Test permissions for folder updating with PUT and archived project""" + """Test PUT with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 200, method='PUT', data=self.put_data + self.url, self.superuser, 200, method='PUT', data=self.put_data ) self.assert_response_api( - self.url, bad_users, 403, method='PUT', data=self.put_data + self.url, + self.auth_non_superusers, + 403, + method='PUT', + data=self.put_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='PUT', data=self.put_data @@ -339,35 +343,45 @@ def test_put_archive(self): self.url, self.user_no_roles, 403, method='PUT', data=self.put_data ) + def test_put_read_only(self): + """Test PUT with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, self.superuser, 200, method='PUT', data=self.put_data + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='PUT', + data=self.put_data, + ) + self.assert_response_api( + self.url, self.anonymous, 401, method='PUT', data=self.put_data + ) + def test_patch(self): """Test FolderRetrieveUpdateDestroyAPIView PATCH""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 200, method='PATCH', data=self.patch_data + self.url, + self.good_users_update, + 200, + method='PATCH', + data=self.patch_data, ) self.assert_response_api( - self.url, bad_users, 403, method='PATCH', data=self.patch_data + self.url, + self.bad_users_update, + 403, + method='PATCH', + data=self.patch_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='PATCH', data=self.patch_data ) self.assert_response_api( self.url, - good_users, + self.good_users_update, 200, method='PATCH', data=self.patch_data, @@ -393,24 +407,15 @@ def test_patch_anon(self): def test_patch_archive(self): """Test PATCH with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 200, method='PATCH', data=self.patch_data + self.url, self.superuser, 200, method='PATCH', data=self.patch_data ) self.assert_response_api( - self.url, bad_users, 403, method='PATCH', data=self.patch_data + self.url, + self.auth_non_superusers, + 403, + method='PATCH', + data=self.patch_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='PATCH', data=self.patch_data @@ -424,35 +429,39 @@ def test_patch_archive(self): data=self.patch_data, ) + def test_patch_read_only(self): + """Test PATCH with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, self.superuser, 200, method='PATCH', data=self.patch_data + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='PATCH', + data=self.patch_data, + ) + self.assert_response_api( + self.url, self.anonymous, 401, method='PATCH', data=self.patch_data + ) + def test_delete(self): """Test FolderRetrieveUpdateDestroyAPIView DELETE""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.good_users_update, 204, method='DELETE', cleanup_method=self._make_folder, ) - self.assert_response_api(self.url, bad_users, 403, method='DELETE') + self.assert_response_api( + self.url, self.bad_users_update, 403, method='DELETE' + ) self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') self.assert_response_api( self.url, - good_users, + self.good_users_update, 204, method='DELETE', cleanup_method=self._make_folder, @@ -470,35 +479,39 @@ def test_delete_anon(self): self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') def test_delete_archive(self): - """Test DELETEwith archived project""" + """Test DELETE with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.superuser, 204, method='DELETE', cleanup_method=self._make_folder, ) - self.assert_response_api(self.url, bad_users, 403, method='DELETE') + self.assert_response_api( + self.url, self.auth_non_superusers, 403, method='DELETE' + ) self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') self.project.set_public() self.assert_response_api( self.url, self.user_no_roles, 403, method='DELETE' ) + def test_delete_read_only(self): + """Test DELETE with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, + self.superuser, + 204, + method='DELETE', + cleanup_method=self._make_folder, + ) + self.assert_response_api( + self.url, self.auth_non_superusers, 403, method='DELETE' + ) + self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') + class TestFileListCreateAPIView(FilesfoldersAPIPermissionTestBase): """Tests for FileListCreateAPIView permissions""" @@ -524,15 +537,7 @@ def setUp(self): 'filesfolders:api_file_list_create', kwargs={'project': self.project.sodar_uuid}, ) - - def tearDown(self): - if hasattr(self, 'post_data'): - self.post_data['file'].close() - super().tearDown() - - def test_get(self): - """Test FileListCreateAPIView GET""" - good_users = [ + self.good_users_get = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -543,39 +548,45 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200) - self.assert_response_api(self.url, bad_users, 403) + self.bad_users_get = [self.user_finder_cat, self.user_no_roles] + + def tearDown(self): + if hasattr(self, 'post_data'): + self.post_data['file'].close() + super().tearDown() + + def test_get(self): + """Test FileListCreateAPIView GET""" + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_get_anon(self): - """Test permissions for file listing with anonymous access""" + """Test GET with anonymous access""" self.project.set_public() self.assert_response_api(self.url, self.anonymous, 200) def test_get_archive(self): - """Test permissions for file listing with archived project""" + """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200) - self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) + self.project.set_public() + self.assert_response_api(self.url, self.user_no_roles, 200) + + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) + self.assert_response_api(self.url, self.anonymous, 401) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) @@ -645,7 +656,6 @@ def test_post(self): data=self.post_data, cleanup_method=self._cleanup, ) - # self.request_data['file'].close() @override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True) def test_post_anon(self): @@ -661,46 +671,67 @@ def test_post_anon(self): data=self.post_data, cleanup_method=self._cleanup, ) - - def test_post_archive(self): - """Test POST with archived project""" - self._make_post_data() - self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] + + def test_post_archive(self): + """Test POST with archived project""" + self._make_post_data() + self.project.set_archive() + self.assert_response_api( + self.url, + self.superuser, + 201, + method='POST', + format='multipart', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='POST', + format='multipart', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response_api( + self.url, + self.anonymous, + 401, + method='POST', + format='multipart', + data=self.post_data, + cleanup_method=self._cleanup, + ) self.assert_response_api( self.url, - good_users, + self.superuser, 201, method='POST', format='multipart', data=self.post_data, cleanup_method=self._cleanup, + knox=True, ) + self.project.set_public() self.assert_response_api( self.url, - bad_users, + self.user_no_roles, 403, method='POST', format='multipart', data=self.post_data, cleanup_method=self._cleanup, ) + + def test_post_read_only(self): + """Test POST with site read-only mode""" + self._make_post_data() + self.set_site_read_only() self.assert_response_api( self.url, - self.anonymous, - 401, + self.superuser, + 201, method='POST', format='multipart', data=self.post_data, @@ -708,19 +739,17 @@ def test_post_archive(self): ) self.assert_response_api( self.url, - good_users, - 201, + self.auth_non_superusers, + 403, method='POST', format='multipart', data=self.post_data, cleanup_method=self._cleanup, - knox=True, ) - self.project.set_public() self.assert_response_api( self.url, - self.user_no_roles, - 403, + self.anonymous, + 401, method='POST', format='multipart', data=self.post_data, @@ -758,10 +787,7 @@ def setUp(self): 'filesfolders:api_file_retrieve_update_destroy', kwargs={'file': file.sodar_uuid}, ) - - def test_get(self): - """Test FileRetrieveUpdateDestroyAPIView GET""" - good_users = [ + self.good_users_get = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -772,11 +798,29 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200) - self.assert_response_api(self.url, bad_users, 403) + self.bad_users_get = [self.user_finder_cat, self.user_no_roles] + self.good_users_update = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_owner, + self.user_delegate, + ] + self.bad_users_update = [ + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_contributor, + self.user_guest, + self.user_no_roles, + ] + + def test_get(self): + """Test FileRetrieveUpdateDestroyAPIView GET""" + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) @@ -789,46 +833,26 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200) - self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) + self.assert_response_api(self.url, self.anonymous, 401) + def test_put(self): """Test FileRetrieveUpdateDestroyAPIView PUT""" self._make_put_data() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.good_users_update, 200, method='PUT', format='multipart', @@ -837,7 +861,7 @@ def test_put(self): ) self.assert_response_api( self.url, - bad_users, + self.bad_users_update, 403, method='PUT', format='multipart', @@ -855,7 +879,7 @@ def test_put(self): ) self.assert_response_api( self.url, - good_users, + self.good_users_update, 200, method='PUT', format='multipart', @@ -891,22 +915,9 @@ def test_put_archive(self): """Test PUT with archived project""" self._make_put_data() self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.superuser, 200, method='PUT', format='multipart', @@ -915,7 +926,7 @@ def test_put_archive(self): ) self.assert_response_api( self.url, - bad_users, + self.auth_non_superusers, 403, method='PUT', format='multipart', @@ -941,26 +952,43 @@ def test_put_archive(self): data=self.put_data, ) + def test_put_read_only(self): + """Test PUT with site read-only mode""" + self._make_put_data() + self.set_site_read_only() + self.assert_response_api( + self.url, + self.superuser, + 200, + method='PUT', + format='multipart', + data=self.put_data, + cleanup_method=self._cleanup_put, + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='PUT', + format='multipart', + data=self.put_data, + cleanup_method=self._cleanup_put, + ) + self.assert_response_api( + self.url, + self.anonymous, + 401, + method='PUT', + format='multipart', + data=self.put_data, + cleanup_method=self._cleanup_put, + ) + def test_patch(self): """Test FileRetrieveUpdateDestroyAPIView PATCH""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.good_users_update, 200, method='PATCH', format='multipart', @@ -968,7 +996,7 @@ def test_patch(self): ) self.assert_response_api( self.url, - bad_users, + self.bad_users_update, 403, method='PATCH', format='multipart', @@ -984,7 +1012,7 @@ def test_patch(self): ) self.assert_response_api( self.url, - good_users, + self.good_users_update, 200, method='PATCH', format='multipart', @@ -1017,22 +1045,9 @@ def test_patch_anon(self): def test_patch_archive(self): """Test PATCH with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.superuser, 200, method='PATCH', format='multipart', @@ -1040,7 +1055,7 @@ def test_patch_archive(self): ) self.assert_response_api( self.url, - bad_users, + self.auth_non_superusers, 403, method='PATCH', format='multipart', @@ -1065,35 +1080,50 @@ def test_patch_archive(self): data=self.patch_data, ) + def test_patch_read_only(self): + """Test PATCH with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, + self.superuser, + 200, + method='PATCH', + format='multipart', + data=self.patch_data, + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='PATCH', + format='multipart', + data=self.patch_data, + ) + self.assert_response_api( + self.url, + self.anonymous, + 401, + method='PATCH', + format='multipart', + data=self.patch_data, + ) + def test_delete(self): """Test FileRetrieveUpdateDestroyAPIView DELETE""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.good_users_update, 204, method='DELETE', cleanup_method=self._make_file, ) - self.assert_response_api(self.url, bad_users, 403, method='DELETE') + self.assert_response_api( + self.url, self.bad_users_update, 403, method='DELETE' + ) self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') self.assert_response_api( self.url, - good_users, + self.good_users_update, 204, method='DELETE', cleanup_method=self._make_file, @@ -1116,33 +1146,37 @@ def test_delete_anon(self): def test_delete_archive(self): """Test DELETE with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.superuser, 204, method='DELETE', cleanup_method=self._make_file, ) - self.assert_response_api(self.url, bad_users, 403, method='DELETE') + self.assert_response_api( + self.url, self.auth_non_superusers, 403, method='DELETE' + ) self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') self.project.set_public() self.assert_response_api( self.url, self.user_no_roles, 403, method='DELETE' ) + def test_delete_read_only(self): + """Test DELETE with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, + self.superuser, + 204, + method='DELETE', + cleanup_method=self._make_file, + ) + self.assert_response_api( + self.url, self.auth_non_superusers, 403, method='DELETE' + ) + self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') + class TestFileServeAPIView(FilesfoldersAPIPermissionTestBase): """Tests for FileServeAPIView permissions""" @@ -1153,10 +1187,7 @@ def setUp(self): self.url = reverse( 'filesfolders:api_file_serve', kwargs={'file': file.sodar_uuid} ) - - def test_get(self): - """Test FileServeAPIView GET""" - good_users = [ + self.good_users = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -1167,11 +1198,14 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200, method='GET') - self.assert_response_api(self.url, bad_users, 403) + self.bad_users = [self.user_finder_cat, self.user_no_roles] + + def test_get(self): + """Test FileServeAPIView GET""" + self.assert_response_api(self.url, self.good_users, 200, method='GET') + self.assert_response_api(self.url, self.bad_users, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) @@ -1184,25 +1218,20 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200, method='GET') - self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.good_users, 200, method='GET') + self.assert_response_api(self.url, self.bad_users, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response_api(self.url, self.good_users, 200, method='GET') + self.assert_response_api(self.url, self.bad_users, 403) + self.assert_response_api(self.url, self.anonymous, 401) + class TestHyperLinkListCreateAPIView(FilesfoldersAPIPermissionTestBase): """Tests for HyperLinkListCreateAPIView permissions""" @@ -1223,10 +1252,7 @@ def setUp(self): 'description': 'Description', 'url': 'https://www.cubi.bihealth.org', } - - def test_get(self): - """Test HyperLinkListCreateAPIView GET""" - good_users = [ + self.good_users_get = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -1237,11 +1263,14 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200) - self.assert_response_api(self.url, bad_users, 403) + self.bad_users_get = [self.user_finder_cat, self.user_no_roles] + + def test_get(self): + """Test HyperLinkListCreateAPIView GET""" + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) @@ -1254,25 +1283,20 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200) - self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response_api(self.url, self.good_users_get, 200) + self.assert_response_api(self.url, self.bad_users_get, 403) + self.assert_response_api(self.url, self.anonymous, 401) + def test_post(self): """Test HyperLinkListCreateAPIView POST""" good_users = [ @@ -1334,36 +1358,27 @@ def test_post_anon(self): def test_post_archive(self): """Test POST with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.superuser, 201, method='POST', data=self.post_data, cleanup_method=self._cleanup, ) self.assert_response_api( - self.url, bad_users, 403, method='POST', data=self.post_data + self.url, + self.auth_non_superusers, + 403, + method='POST', + data=self.post_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='POST', data=self.post_data ) self.assert_response_api( self.url, - good_users, + self.superuser, 201, method='POST', data=self.post_data, @@ -1379,6 +1394,28 @@ def test_post_archive(self): data=self.post_data, ) + def test_post_read_only(self): + """Test POST with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, + self.superuser, + 201, + method='POST', + data=self.post_data, + cleanup_method=self._cleanup, + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='POST', + data=self.post_data, + ) + self.assert_response_api( + self.url, self.anonymous, 401, method='POST', data=self.post_data + ) + class TestHyperLinkRetrieveUpdateDestroyAPIView( FilesfoldersAPIPermissionTestBase @@ -1408,10 +1445,7 @@ def setUp(self): 'url': 'https://www.bihealth.org', } self.patch_data = {'name': 'UPDATED Hyperlink'} - - def test_get(self): - """Test HyperLinkRetrieveUpdateDestroyAPIView GET""" - good_users = [ + self.good_users_get = [ self.superuser, self.user_owner_cat, self.user_delegate_cat, @@ -1422,11 +1456,31 @@ def test_get(self): self.user_contributor, self.user_guest, ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200, method='GET') - self.assert_response_api(self.url, bad_users, 403) + self.bad_users_get = [self.user_finder_cat, self.user_no_roles] + self.good_users_update = [ + self.superuser, + self.user_owner_cat, + self.user_delegate_cat, + self.user_owner, # Owner of link + self.user_delegate, + ] + self.bad_users_update = [ + self.user_contributor_cat, + self.user_guest_cat, + self.user_finder_cat, + self.user_contributor, + self.user_guest, + self.user_no_roles, + ] + + def test_get(self): + """Test HyperLinkRetrieveUpdateDestroyAPIView GET""" + self.assert_response_api( + self.url, self.good_users_get, 200, method='GET' + ) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) @@ -1439,54 +1493,46 @@ def test_get_anon(self): def test_get_archive(self): """Test GET with archived project""" self.project.set_archive() - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - ] - bad_users = [self.user_finder_cat, self.user_no_roles] - self.assert_response_api(self.url, good_users, 200, method='GET') - self.assert_response_api(self.url, bad_users, 403) + self.assert_response_api( + self.url, self.good_users_get, 200, method='GET' + ) + self.assert_response_api(self.url, self.bad_users_get, 403) self.assert_response_api(self.url, self.anonymous, 401) - self.assert_response_api(self.url, good_users, 200, knox=True) + self.assert_response_api(self.url, self.good_users_get, 200, knox=True) self.project.set_public() self.assert_response_api(self.url, self.user_no_roles, 200) + def test_get_read_only(self): + """Test GET with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, self.good_users_get, 200, method='GET' + ) + self.assert_response_api(self.url, self.bad_users_get, 403) + self.assert_response_api(self.url, self.anonymous, 401) + def test_put(self): """Test HyperLinkRetrieveUpdateDestroyAPIView PUT""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, # Owner of link - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 200, method='PUT', data=self.put_data + self.url, + self.good_users_update, + 200, + method='PUT', + data=self.put_data, ) self.assert_response_api( - self.url, bad_users, 403, method='PUT', data=self.put_data + self.url, + self.bad_users_update, + 403, + method='PUT', + data=self.put_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='PUT', data=self.put_data ) self.assert_response_api( self.url, - good_users, + self.good_users_update, 200, method='PUT', data=self.put_data, @@ -1508,31 +1554,22 @@ def test_put_anon(self): def test_put_archive(self): """Test PUT with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 200, method='PUT', data=self.put_data + self.url, self.superuser, 200, method='PUT', data=self.put_data ) self.assert_response_api( - self.url, bad_users, 403, method='PUT', data=self.put_data + self.url, + self.auth_non_superusers, + 403, + method='PUT', + data=self.put_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='PUT', data=self.put_data ) self.assert_response_api( self.url, - good_users, + self.superuser, 200, method='PUT', data=self.put_data, @@ -1543,35 +1580,45 @@ def test_put_archive(self): self.url, self.user_no_roles, 403, method='PUT', data=self.put_data ) + def test_put_read_only(self): + """Test PUT with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, self.superuser, 200, method='PUT', data=self.put_data + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='PUT', + data=self.put_data, + ) + self.assert_response_api( + self.url, self.anonymous, 401, method='PUT', data=self.put_data + ) + def test_patch(self): """Test HyperLinkRetrieveUpdateDestroyAPIView PATCH""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 200, method='PATCH', data=self.patch_data + self.url, + self.good_users_update, + 200, + method='PATCH', + data=self.patch_data, ) self.assert_response_api( - self.url, bad_users, 403, method='PATCH', data=self.patch_data + self.url, + self.bad_users_update, + 403, + method='PATCH', + data=self.patch_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='PATCH', data=self.patch_data ) self.assert_response_api( self.url, - good_users, + self.good_users_update, 200, method='PATCH', data=self.patch_data, @@ -1597,31 +1644,22 @@ def test_patch_anon(self): def test_patch_archive(self): """Test PATCH with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( - self.url, good_users, 200, method='PATCH', data=self.patch_data + self.url, self.superuser, 200, method='PATCH', data=self.patch_data ) self.assert_response_api( - self.url, bad_users, 403, method='PATCH', data=self.patch_data + self.url, + self.auth_non_superusers, + 403, + method='PATCH', + data=self.patch_data, ) self.assert_response_api( self.url, self.anonymous, 401, method='PATCH', data=self.patch_data ) self.assert_response_api( self.url, - good_users, + self.superuser, 200, method='PATCH', data=self.patch_data, @@ -1636,36 +1674,39 @@ def test_patch_archive(self): data=self.patch_data, ) + def test_patch_read_only(self): + """Test PATCH with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, self.superuser, 200, method='PATCH', data=self.patch_data + ) + self.assert_response_api( + self.url, + self.auth_non_superusers, + 403, + method='PATCH', + data=self.patch_data, + ) + self.assert_response_api( + self.url, self.anonymous, 401, method='PATCH', data=self.patch_data + ) + def test_delete(self): """Test HyperLinkRetrieveUpdateDestroyAPIView DELETE""" - good_users = [ - self.superuser, - self.user_owner_cat, - self.user_delegate_cat, - self.user_owner, - self.user_delegate, - ] - bad_users = [ - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] - self.assert_response_api( self.url, - good_users, + self.good_users_update, 204, method='DELETE', cleanup_method=self._cleanup_delete, ) - self.assert_response_api(self.url, bad_users, 403, method='DELETE') + self.assert_response_api( + self.url, self.bad_users_update, 403, method='DELETE' + ) self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') self.assert_response_api( self.url, - good_users, + self.good_users_update, 204, method='DELETE', cleanup_method=self._cleanup_delete, @@ -1689,31 +1730,20 @@ def test_delete_anon(self): def test_delete_archive(self): """Test DELETE with archived project""" self.project.set_archive() - good_users = [self.superuser] - bad_users = [ - self.user_owner_cat, - self.user_delegate_cat, - self.user_contributor_cat, - self.user_guest_cat, - self.user_finder_cat, - self.user_owner, - self.user_delegate, - self.user_contributor, - self.user_guest, - self.user_no_roles, - ] self.assert_response_api( self.url, - good_users, + self.superuser, 204, method='DELETE', cleanup_method=self._cleanup_delete, ) - self.assert_response_api(self.url, bad_users, 403, method='DELETE') + self.assert_response_api( + self.url, self.auth_non_superusers, 403, method='DELETE' + ) self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') self.assert_response_api( self.url, - good_users, + self.superuser, 204, method='DELETE', cleanup_method=self._cleanup_delete, @@ -1723,3 +1753,18 @@ def test_delete_archive(self): self.assert_response_api( self.url, self.user_no_roles, 403, method='DELETE' ) + + def test_delete_read_only(self): + """Test DELETE with site read-only mode""" + self.set_site_read_only() + self.assert_response_api( + self.url, + self.superuser, + 204, + method='DELETE', + cleanup_method=self._cleanup_delete, + ) + self.assert_response_api( + self.url, self.auth_non_superusers, 403, method='DELETE' + ) + self.assert_response_api(self.url, self.anonymous, 401, method='DELETE') diff --git a/projectroles/app_settings.py b/projectroles/app_settings.py index 17c3100f..a8215900 100644 --- a/projectroles/app_settings.py +++ b/projectroles/app_settings.py @@ -131,6 +131,18 @@ user_modifiable=True, global_edit=True, ), + PluginAppSettingDef( + name='site_read_only', + scope=APP_SETTING_SCOPE_SITE, + type=APP_SETTING_TYPE_BOOLEAN, + default=False, + label='Site read-only mode', + description='Set site in read-only mode. Data altering operations will ' + 'be prohibited. Mode must be explicitly unset to allow data' + 'modification.', + user_modifiable=True, + global_edit=False, + ), ] diff --git a/projectroles/rules.py b/projectroles/rules.py index 37a30fbb..2732246b 100644 --- a/projectroles/rules.py +++ b/projectroles/rules.py @@ -2,8 +2,13 @@ from django.conf import settings +from projectroles.app_settings import AppSettingAPI from projectroles.models import RoleAssignment, SODAR_CONSTANTS + +app_settings = AppSettingAPI() + + # SODAR constants PROJECT_ROLE_OWNER = SODAR_CONSTANTS['PROJECT_ROLE_OWNER'] PROJECT_ROLE_DELEGATE = SODAR_CONSTANTS['PROJECT_ROLE_DELEGATE'] @@ -108,7 +113,9 @@ def has_roles(user): @rules.predicate def is_modifiable_project(user, obj): """Whether or not project metadata is modifiable""" - return False if obj.is_remote() else True + if obj.is_remote() or app_settings.get('projectroles', 'site_read_only'): + return False + return True @rules.predicate @@ -117,16 +124,20 @@ def can_modify_project_data(user, obj): Whether or not project app data can be modified, due to e.g. project archiving status. """ - return not obj.archive + return not obj.archive and not app_settings.get( + 'projectroles', 'site_read_only' + ) @rules.predicate def can_create_projects(user, obj): - """Whether or not new projects can be generated on the site""" + """Whether or not new projects can be created on the site""" if settings.PROJECTROLES_SITE_MODE == SITE_MODE_TARGET and ( not settings.PROJECTROLES_TARGET_CREATE or (obj and obj.is_remote()) ): return False + if app_settings.get('projectroles', 'site_read_only'): + return False return True @@ -154,6 +165,12 @@ def is_target_site(): return settings.PROJECTROLES_SITE_MODE == SITE_MODE_TARGET +@rules.predicate +def is_site_writable(): + """Return True if site has not been set in read-only mode""" + return not app_settings.get('projectroles', 'site_read_only') + + # Combined predicates ---------------------------------------------------------- @@ -186,7 +203,7 @@ def is_target_site(): # Allow project updating rules.add_perm( 'projectroles.update_project', - is_project_update_user, + is_project_update_user & is_site_writable, ) # Allow creation of projects @@ -194,10 +211,13 @@ def is_target_site(): 'projectroles.create_project', is_project_create_user & can_create_projects ) +# Allow viewing PROJECT scope settings +rules.add_perm('projectroles.view_project_settings', is_project_update_user) + # Allow updating project settings rules.add_perm( 'projectroles.update_project_settings', - is_role_update_user & is_modifiable_project, + is_project_update_user & is_modifiable_project, ) # Allow viewing project roles @@ -241,3 +261,9 @@ def is_target_site(): rules.add_perm( 'projectroles.view_hidden_projects', rules.is_superuser | is_project_owner ) + +# Allow starring/unstarring a project +rules.add_perm( + 'projectroles.star_project', + (can_view_project | has_category_child_role) & is_site_writable, +) diff --git a/projectroles/static/projectroles/js/projectroles.js b/projectroles/static/projectroles/js/projectroles.js index 640eded9..afaf53fd 100644 --- a/projectroles/static/projectroles/js/projectroles.js +++ b/projectroles/static/projectroles/js/projectroles.js @@ -329,7 +329,42 @@ $(document).ready(function () { $(this).prepend( ''); - $("body").css("cursor", "progress"); + $('body').css('cursor', 'progress'); $(this).closest('form').submit(); }); }); + + +/* Update site read-only mode alert ----------------------------------------- */ + +function updateReadOnlyAlert(url, alert) { + $.ajax({ + url: url, + method: 'GET', + }).done(function (data) { + var siteReadOnly = data['site_read_only']; + if (siteReadOnly === true) { + setTimeout(function() { + updateReadOnlyAlert(url, alert); + }, 5000); + } else { + alert.addClass('alert-success') + .removeClass('alert-danger') + .addClass('sodar-alert-site-read-only-updated'); + alert.find('.sodar-alert-top-content').html( + ' ' + + 'Site read-only mode disabled. Please ' + + 'reload ' + + 'your browser tab.' + ) + } + }); +} + +$(document).ready(function () { + var readOnlyAlert = $(document).find('#sodar-alert-site-read-only'); + if (readOnlyAlert) { + var url = readOnlyAlert.attr('data-url'); + updateReadOnlyAlert(url, readOnlyAlert); + } +}); diff --git a/projectroles/templates/projectroles/_messages.html b/projectroles/templates/projectroles/_messages.html index d9394451..4564ee14 100644 --- a/projectroles/templates/projectroles/_messages.html +++ b/projectroles/templates/projectroles/_messages.html @@ -5,39 +5,56 @@ {% load projectroles_common_tags %} {% get_site_app_messages request.user as site_app_messages %} +{% get_app_setting 'projectroles' 'site_read_only' as site_read_only %} -{% if messages or site_app_messages %} +{% if site_read_only or messages or site_app_messages %}
Use this document as a way to quick start any new project.
{% endblock content %}