Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions docs/src/setup/configuration/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,19 +498,79 @@ Additional download handlers that provides a link to download the resource
One of the main features of debug mode is the display of detailed error pages. If your app raises an exception when DEBUG is True, Django will display a detailed traceback, including a lot of metadata about your environment, such as all the currently defined Django settings (from settings.py).
This is a [Django Setting](https://docs.djangoproject.com/en/3.2/ref/settings/#debug)

**DEFAULT_ANONYMOUS_PERMISSIONS**

: - Default ``None``
- Env: ``DEFAULT_ANONYMOUS_PERMISSIONS``

Defines the default compact permission level assigned to anonymous users when a new resource is created.

Supported values are:

- ``view``
- ``download``
- ``none``

If this setting is not configured, GeoNode falls back to the deprecated ``DEFAULT_ANONYMOUS_VIEW_PERMISSION`` and ``DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION`` settings.

Example:

```python
DEFAULT_ANONYMOUS_PERMISSIONS = "download"
```

If an unsupported value is configured, GeoNode logs a warning and falls back to ``none``.

**DEFAULT_REGISTERED_MEMBERS_PERMISSIONS**

: - Default ``None``
- Env: ``DEFAULT_REGISTERED_MEMBERS_PERMISSIONS``

Defines the default compact permission level assigned to the registered members group when a new resource is created.

Supported values are:

- ``view``
- ``download``
- ``edit``
- ``manage``
- ``none``

When set to ``none``, no default permissions are assigned to the registered members group.

Example:

```python
DEFAULT_REGISTERED_MEMBERS_PERMISSIONS = "edit"
```

If an unsupported value is configured, GeoNode logs a warning and falls back to ``none``.

[](){ #default-anonymous-download-permission }
**DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION**

: Default: ``True``
: - Default: ``True``
- Env: ``DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION``

Deprecated. Use ``DEFAULT_ANONYMOUS_PERMISSIONS`` instead.

Whether uploaded resources should be downloadable by anonymous users by default.

This legacy setting is used only when ``DEFAULT_ANONYMOUS_PERMISSIONS`` is not configured. When ``DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION`` is ``True``, GeoNode treats the anonymous default compact permission as ``download``.

Whether the uploaded resources should downloadable by default.

[](){ #default-anonymous-view-permission }
**DEFAULT_ANONYMOUS_VIEW_PERMISSION**

: Default: ``True``
: - Default: ``True``
- Env: ``DEFAULT_ANONYMOUS_VIEW_PERMISSION``

Deprecated. Use ``DEFAULT_ANONYMOUS_PERMISSIONS`` instead.

Whether uploaded resources should be visible to anonymous users by default.

This legacy setting is used only when ``DEFAULT_ANONYMOUS_PERMISSIONS`` is not configured. When ``DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION`` is ``False`` and ``DEFAULT_ANONYMOUS_VIEW_PERMISSION`` is ``True``, GeoNode treats the anonymous default compact permission as ``view``.

Whether the uploaded resources should be public by default.


**DEFAULT_DATASET_DOWNLOAD_HANDLER**
Expand Down
38 changes: 38 additions & 0 deletions geonode/base/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2357,6 +2357,44 @@ def test_manager_can_edit_map(self):
resource_perm_spec,
)

@override_settings(
DEFAULT_ANONYMOUS_PERMISSIONS="download",
DEFAULT_REGISTERED_MEMBERS_PERMISSIONS="edit",
)
def test_resource_service_permissions_default_groups_from_compact_settings(self):
self.assertTrue(self.client.login(username="admin", password="admin"))
admin = get_user_model().objects.get(username="admin")
dataset = dataset_manager.create(
str(uuid4()), resource_type=Dataset, defaults={"title": "api_perms_compact_default", "owner": admin}
)
url = reverse("base-resources-perms-spec", kwargs={"pk": dataset.pk})
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, 200)

group_permissions = {g["name"]: g["permissions"] for g in response.data.get("groups", [])}
self.assertEqual(group_permissions.get("anonymous"), "download")
self.assertEqual(group_permissions.get("registered-members"), "edit")

@override_settings(
DEFAULT_ANONYMOUS_PERMISSIONS=None,
DEFAULT_ANONYMOUS_VIEW_PERMISSION=True,
DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=False,
DEFAULT_REGISTERED_MEMBERS_PERMISSIONS=None,
)
def test_resource_service_permissions_default_groups_from_legacy_settings(self):
self.assertTrue(self.client.login(username="admin", password="admin"))
admin = get_user_model().objects.get(username="admin")
dataset = dataset_manager.create(
str(uuid4()), resource_type=Dataset, defaults={"title": "api_perms_legacy_default", "owner": admin}
)
url = reverse("base-resources-perms-spec", kwargs={"pk": dataset.pk})
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, 200)

group_permissions = {g["name"]: g["permissions"] for g in response.data.get("groups", [])}
self.assertEqual(group_permissions.get("anonymous"), "view")
self.assertEqual(group_permissions.get("registered-members"), "none")

@override_settings(
EDITORS_CAN_MANAGE_ANONYMOUS_PERMISSIONS=False,
EDITORS_CAN_MANAGE_REGISTERED_MEMBERS_PERMISSIONS=False,
Expand Down
12 changes: 10 additions & 2 deletions geonode/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,21 @@
from geonode.notifications_helper import has_notifications
from geonode.base.models import Configuration, Thesaurus
from geonode.utils import get_geonode_app_types
from geonode.security.permissions import (
DOWNLOAD_RIGHTS,
VIEW_RIGHTS,
get_default_anonymous_compact_permission,
)

from allauth.socialaccount.models import SocialApp


def resource_urls(request):
"""Global values to pass to templates"""
site = Site.objects.get_current()
anonymous_compact = get_default_anonymous_compact_permission()
default_anonymous_view = anonymous_compact in (VIEW_RIGHTS, DOWNLOAD_RIGHTS)
default_anonymous_download = anonymous_compact == DOWNLOAD_RIGHTS
thesaurus = Thesaurus.objects.filter(facet=True).all().order_by("order", "id")
if hasattr(settings, "THESAURUS"):
warnings.warn(
Expand Down Expand Up @@ -76,8 +84,8 @@ def resource_urls(request):
LICENSES_METADATA=getattr(settings, "LICENSES", dict()).get("METADATA", "never"),
USE_GEOSERVER=getattr(settings, "USE_GEOSERVER", False),
USE_NOTIFICATIONS=has_notifications,
DEFAULT_ANONYMOUS_VIEW_PERMISSION=getattr(settings, "DEFAULT_ANONYMOUS_VIEW_PERMISSION", False),
DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=getattr(settings, "DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION", False),
DEFAULT_ANONYMOUS_VIEW_PERMISSION=default_anonymous_view,
DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=default_anonymous_download,
EXIF_ENABLED=getattr(settings, "EXIF_ENABLED", False),
FAVORITE_ENABLED=getattr(settings, "FAVORITE_ENABLED", False),
THESAURI_FILTERS=(
Expand Down
9 changes: 6 additions & 3 deletions geonode/geoserver/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
from geonode.services.enumerations import CASCADED
from geonode.security.utils import skip_registered_members_common_group
from geonode.security.permissions import (
get_default_anonymous_compact_permission,
VIEW_RIGHTS,
DOWNLOAD_RIGHTS,
VIEW_PERMISSIONS,
OWNER_PERMISSIONS,
DOWNLOAD_PERMISSIONS,
Expand Down Expand Up @@ -244,13 +247,13 @@ def set_permissions(
if not skip_registered_members_common_group(user_group):
create_geofence_rules(_resource, perms, None, user_group, batch)
exist_geolimits = exist_geolimits or has_geolimits(_resource, None, user_group)

# Anonymous
if settings.DEFAULT_ANONYMOUS_VIEW_PERMISSION:
anonymous_compact = get_default_anonymous_compact_permission()
if anonymous_compact in (VIEW_RIGHTS, DOWNLOAD_RIGHTS):
create_geofence_rules(_resource, VIEW_PERMISSIONS, None, None, batch)
exist_geolimits = exist_geolimits or has_geolimits(_resource, None, None)

if settings.DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION:
if anonymous_compact == DOWNLOAD_RIGHTS:
create_geofence_rules(_resource, DOWNLOAD_PERMISSIONS, None, None, batch)
exist_geolimits = exist_geolimits or has_geolimits(_resource, None, None)

Expand Down
3 changes: 0 additions & 3 deletions geonode/resource/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,9 @@ def finalize_creation_permissions(
) -> bool:
"""
Finalize default permissions for newly created resources,
including optional creation-time ownership handling.
"""
if not instance:
return False
if not getattr(settings, "AUTO_ASSIGN_RESOURCE_OWNERSHIP_TO_ADMIN", False):
return False
instance.set_default_permissions(owner=owner or instance.owner, created=True, initial_user=initial_user)
return True

Expand Down
45 changes: 44 additions & 1 deletion geonode/security/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
#########################################################################
from abc import ABC
from django.conf import settings
from geonode.security.permissions import _to_extended_perms, MANAGE_RIGHTS
from geonode.security.permissions import (
_to_extended_perms,
get_default_anonymous_compact_permission,
get_default_registered_members_compact_permission,
MANAGE_RIGHTS,
)
from geonode.groups.conf import settings as groups_settings
from django.contrib.auth.models import Group


class BasePermissionsHandler(ABC):
Expand Down Expand Up @@ -95,6 +102,42 @@ def _has_edit(perms_list, u):
return perms_copy


class DefaultSpecialGroupsPermissionsHandler(BasePermissionsHandler):
"""
Auto-assign configured permissions to anonymous and registered members groups on creation.
"""

@staticmethod
def fixup_perms(instance, perms_payload, include_virtual=True, *args, **kwargs):
if not kwargs.get("created", False):
return perms_payload

payload = perms_payload or {}
payload.setdefault("groups", {})

_resource_type = getattr(instance, "resource_type", None) or instance.polymorphic_ctype.name
_resource_subtype = (getattr(instance, "subtype", None) or "").lower()

anonymous_compact = get_default_anonymous_compact_permission()
anonymous_group, _ = Group.objects.get_or_create(name="anonymous")
payload["groups"][anonymous_group] = sorted(
_to_extended_perms(anonymous_compact, _resource_type, _resource_subtype)
)

registered_compact = get_default_registered_members_compact_permission()
try:
registered_group = Group.objects.get(name=groups_settings.REGISTERED_MEMBERS_GROUP_NAME)
except Group.DoesNotExist:
registered_group = None

if registered_group:
payload["groups"][registered_group] = sorted(
_to_extended_perms(registered_compact, _resource_type, _resource_subtype)
)

return payload


class AdvancedWorkflowPermissionsHandler(BasePermissionsHandler):
"""
Handler that takes care of adjusting the permissions for the advanced workflow
Expand Down
11 changes: 8 additions & 3 deletions geonode/security/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
from functools import reduce

from django.db.models import Q
from django.conf import settings
from geonode.security.permissions import (
get_default_anonymous_compact_permission,
VIEW_RIGHTS,
DOWNLOAD_RIGHTS,
)
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import Group, Permission
Expand Down Expand Up @@ -198,7 +202,8 @@ def set_default_permissions(self, owner=None, created=False, **kwargs):
user_groups = Group.objects.filter(name__in=_owner.groupmember_set.values_list("group__slug", flat=True))

# Anonymous
anonymous_can_view = settings.DEFAULT_ANONYMOUS_VIEW_PERMISSION
anonymous_compact = get_default_anonymous_compact_permission()
anonymous_can_view = anonymous_compact == VIEW_RIGHTS
if anonymous_can_view:
perm_spec["groups"][anonymous_group] = ["view_resourcebase"]
else:
Expand All @@ -211,7 +216,7 @@ def set_default_permissions(self, owner=None, created=False, **kwargs):
):
perm_spec["groups"][user_group] = ["view_resourcebase"]

anonymous_can_download = settings.DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION
anonymous_can_download = anonymous_compact == DOWNLOAD_RIGHTS
if anonymous_can_download:
perm_spec["groups"][anonymous_group] = ["view_resourcebase", "download_resourcebase"]
else:
Expand Down
71 changes: 63 additions & 8 deletions geonode/security/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import copy
import json
import logging
import pprint
import jsonschema
import collections
Expand All @@ -32,6 +33,9 @@
from geonode.utils import build_absolute_uri
from geonode.groups.conf import settings as groups_settings

logger = logging.getLogger(__name__)


"""
Permissions will be managed according to a "compact" set:

Expand Down Expand Up @@ -113,14 +117,6 @@

SERVICE_PERMISSIONS = ["add_service", "delete_service", "change_resourcebase_metadata", "add_resourcebase_from_service"]

DEFAULT_PERMISSIONS = []
if settings.DEFAULT_ANONYMOUS_VIEW_PERMISSION:
DEFAULT_PERMISSIONS += VIEW_PERMISSIONS
if settings.DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION:
DEFAULT_PERMISSIONS += DOWNLOAD_PERMISSIONS

DEFAULT_PERMS_SPEC = json.dumps({"users": {"AnonymousUser": DEFAULT_PERMISSIONS}, "groups": {}})

NONE_RIGHTS = "none"
VIEW_RIGHTS = "view"
DOWNLOAD_RIGHTS = "download"
Expand All @@ -136,6 +132,65 @@
(OWNER_RIGHTS, "owner"),
)

VALID_ANONYMOUS_COMPACT_PERMISSIONS = {VIEW_RIGHTS, DOWNLOAD_RIGHTS, NONE_RIGHTS}
VALID_REGISTERED_MEMBERS_COMPACT_PERMISSIONS = {VIEW_RIGHTS, DOWNLOAD_RIGHTS, EDIT_RIGHTS, MANAGE_RIGHTS, NONE_RIGHTS}


def _normalize_compact_permission(raw_value, valid_values, setting_name):
if raw_value is None:
return None
normalized_value = str(raw_value).strip().lower()
if normalized_value in ("", NONE_RIGHTS):
return None
if normalized_value not in valid_values:
logger.warning(
"%s contains unsupported value '%s'. Defaulting to 'none'.",
setting_name,
normalized_value,
)
return None
return normalized_value


def get_default_anonymous_compact_permission():
raw_value = getattr(settings, "DEFAULT_ANONYMOUS_PERMISSIONS", None)
if raw_value is not None:
return _normalize_compact_permission(
raw_value,
VALID_ANONYMOUS_COMPACT_PERMISSIONS,
"DEFAULT_ANONYMOUS_PERMISSIONS",
)
legacy_download = getattr(settings, "DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION", True)
legacy_view = getattr(settings, "DEFAULT_ANONYMOUS_VIEW_PERMISSION", True)
if legacy_download:
return DOWNLOAD_RIGHTS
if legacy_view:
return VIEW_RIGHTS
return None


def get_default_registered_members_compact_permission():
raw_value = getattr(settings, "DEFAULT_REGISTERED_MEMBERS_PERMISSIONS", None)
if raw_value is None:
return None
return _normalize_compact_permission(
raw_value,
VALID_REGISTERED_MEMBERS_COMPACT_PERMISSIONS,
"DEFAULT_REGISTERED_MEMBERS_PERMISSIONS",
)


def get_default_anonymous_permissions_list():
compact_perm = get_default_anonymous_compact_permission()
if compact_perm == VIEW_RIGHTS:
return VIEW_PERMISSIONS
if compact_perm == DOWNLOAD_RIGHTS:
return VIEW_PERMISSIONS + DOWNLOAD_PERMISSIONS
return []


DEFAULT_PERMISSIONS = get_default_anonymous_permissions_list()
DEFAULT_PERMS_SPEC = json.dumps({"users": {"AnonymousUser": DEFAULT_PERMISSIONS}, "groups": {}})

PERM_SPEC_COMPACT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
Expand Down
Loading
Loading