diff --git a/docs/tethys_portal/configuration.rst b/docs/tethys_portal/configuration.rst index 6de3161ef..6ded214ed 100644 --- a/docs/tethys_portal/configuration.rst +++ b/docs/tethys_portal/configuration.rst @@ -81,7 +81,7 @@ Setting Description ENABLE_OPEN_SIGNUP anyone can create a Tethys Portal account using a "Sign Up" link on the home page when ``True``. Defaults to ``False``. REGISTER_CONTROLLER override the default registration page with a custom controller. The value should be the dot-path to the controller function/class (e.g. ``tethysext.my_extension.controllers.custom_registration``) ENABLE_OPEN_PORTAL no login required for Tethys Portal when ``True``. Defaults to ``False``. Controllers in apps need to use the ``controller`` decorator from the Tethys SDK, rather than Django's ``login_required`` decorator. -ENABLE_RESTRICTED_APP_ACCESS app access can be restricted based on user object permissions when ``True``. Defaults to ``False``. If ``ENABLE_OPEN_PORTAL`` is set to ``True`` this setting has no effect. That is, users will have unrestricted access to apps independently of the value of this setting. +ENABLE_RESTRICTED_APP_ACCESS app access can be restricted based on user object permissions when ``True``. Defaults to ``False``. A list can also be provided to restrict specific applications. If ``ENABLE_OPEN_PORTAL`` is set to ``True`` this setting has no effect. That is, users will have unrestricted access to apps independently of the value of this setting. TETHYS_WORKSPACES_ROOT location to where app/user workspaces will be created. Defaults to :file:`/workspaces`. STATIC_ROOT the Django `STATIC_ROOT `_ setting. Defaults to :file:`/static`. MEDIA_URL the Django `MEDIA_URL `_ setting. Defaults to ``'/media/'``. diff --git a/scripts/generate_portal_config_tables.py b/scripts/generate_portal_config_tables.py index ddab9f54e..69ecc97b0 100644 --- a/scripts/generate_portal_config_tables.py +++ b/scripts/generate_portal_config_tables.py @@ -20,7 +20,7 @@ "ENABLE_OPEN_SIGNUP": 'anyone can create a Tethys Portal account using a "Sign Up" link on the home page when ``True``. Defaults to ``False``.', "REGISTER_CONTROLLER": "override the default registration page with a custom controller. The value should be the dot-path to the controller function/class (e.g. ``tethysext.my_extension.controllers.custom_registration``)", "ENABLE_OPEN_PORTAL": "no login required for Tethys Portal when ``True``. Defaults to ``False``. Controllers in apps need to use the ``controller`` decorator from the Tethys SDK, rather than Django's ``login_required`` decorator.", - "ENABLE_RESTRICTED_APP_ACCESS": "app access can be restricted based on user object permissions when ``True``. Defaults to ``False``. If ``ENABLE_OPEN_PORTAL`` is set to ``True`` this setting has no effect. That is, users will have unrestricted access to apps independently of the value of this setting.", + "ENABLE_RESTRICTED_APP_ACCESS": "app access can be restricted based on user object permissions when ``True``. Defaults to ``False``. A list can also be provided to restrict specific applications. If ``ENABLE_OPEN_PORTAL`` is set to ``True`` this setting has no effect. That is, users will have unrestricted access to apps independently of the value of this setting.", "TETHYS_WORKSPACES_ROOT": "location to which app workspaces will be synced when ``tethys manage collectworkspaces`` is executed. Gathering all workspaces to one location is recommended for production deployments to allow for easier updating and backing up of app data. Defaults to :file:`/workspaces`.", "STATIC_ROOT": f"the Django `STATIC_ROOT `_ setting. Defaults to :file:`/static`.", "STATICFILES_USE_NPM": "serves JavaScript dependencies through Tethys rather than using a content delivery network (CDN) when ``True``. Defaults to ``False``. When set to ``True`` then you must run ``tethys gen package_json`` to npm install the JS dependencies locally so they can be served by Tethys.", diff --git a/tests/unit_tests/test_tethys_apps/test_utilities.py b/tests/unit_tests/test_tethys_apps/test_utilities.py index 4ac09bbbc..2e91a30b4 100644 --- a/tests/unit_tests/test_tethys_apps/test_utilities.py +++ b/tests/unit_tests/test_tethys_apps/test_utilities.py @@ -858,16 +858,36 @@ def test_user_can_access_app(self, mock_settings): result1 = utilities.user_can_access_app(user, app) self.assertFalse(result1) + # test restricted access with restricted apps list + mock_settings.ENABLE_RESTRICTED_APP_ACCESS = [app.package] + result2 = utilities.user_can_access_app(user, app) + self.assertFalse(result2) + + # test restricted access with no restricted apps list + mock_settings.ENABLE_RESTRICTED_APP_ACCESS = [] + result3 = utilities.user_can_access_app(user, app) + self.assertTrue(result3) + + # test restricted access with restricted apps list of a different app + mock_settings.ENABLE_RESTRICTED_APP_ACCESS = ["some_other_app"] + result4 = utilities.user_can_access_app(user, app) + self.assertTrue(result4) + # test with permission assign_perm(f"{app.package}:access_app", user, app) - result2 = utilities.user_can_access_app(user, app) - self.assertTrue(result2) + result5 = utilities.user_can_access_app(user, app) + self.assertTrue(result5) # test open portal mode case mock_settings.ENABLE_OPEN_PORTAL = True - result3 = utilities.user_can_access_app(user, app) - self.assertTrue(result3) + result6 = utilities.user_can_access_app(user, app) + self.assertTrue(result6) + + # test restricted access with restricted apps list + mock_settings.ENABLE_RESTRICTED_APP_ACCESS = [app.package] + result7 = utilities.user_can_access_app(user, app) + self.assertTrue(result7) def test_get_installed_tethys_items_apps(self): # Get list of apps installed in the tethysapp directory diff --git a/tethys_apps/utilities.py b/tethys_apps/utilities.py index 06444aec5..4bdd60469 100644 --- a/tethys_apps/utilities.py +++ b/tethys_apps/utilities.py @@ -586,10 +586,17 @@ def get_service_model_from_type(service_type): def user_can_access_app(user, app): from django.conf import settings + RESTRICTED_APP_ACCESS = getattr(settings, "ENABLE_RESTRICTED_APP_ACCESS", False) + if getattr(settings, "ENABLE_OPEN_PORTAL", False): return True - elif getattr(settings, "ENABLE_RESTRICTED_APP_ACCESS", False): - return user.has_perm(f"{app.package}:access_app", app) + elif RESTRICTED_APP_ACCESS: + if isinstance(RESTRICTED_APP_ACCESS, bool): + return user.has_perm(f"{app.package}:access_app", app) + elif app.package in RESTRICTED_APP_ACCESS: + return user.has_perm(f"{app.package}:access_app", app) + else: + return True else: return True diff --git a/tethys_portal/settings.py b/tethys_portal/settings.py index ab0829104..98b825ab5 100644 --- a/tethys_portal/settings.py +++ b/tethys_portal/settings.py @@ -82,7 +82,8 @@ # Set to True to allow Open Portal mode. This mode supersedes any specific user/group app access permissions ENABLE_OPEN_PORTAL = TETHYS_PORTAL_CONFIG.pop("ENABLE_OPEN_PORTAL", False) -# Set to True to allow Open Portal mode. This mode supersedes any specific user/group app access permissions +# Set to True to allow restricted app access. This mode removes access to apps for nonadmins unless given explicit permission. +# A list can also be provided to restrict specific applications unless users are given explicit permission ENABLE_RESTRICTED_APP_ACCESS = TETHYS_PORTAL_CONFIG.pop( "ENABLE_RESTRICTED_APP_ACCESS", False )