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
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
35 changes: 34 additions & 1 deletion geonode/security/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@
from geonode.layers.models import Dataset
from geonode.documents.models import Document
from geonode.compat import ensure_string
from geonode.security.handlers import BasePermissionsHandler, GroupManagersPermissionsHandler
from geonode.security.handlers import (
BasePermissionsHandler,
GroupManagersPermissionsHandler,
DefaultSpecialGroupsPermissionsHandler,
)
from geonode.upload.models import ResourceHandlerInfo
from geonode.utils import check_ogc_backend, build_absolute_uri
from geonode.tests.utils import check_dataset
Expand All @@ -60,6 +64,8 @@
from geonode.layers.populate_datasets_data import create_dataset_data
from geonode.base.auth import create_auth_token, get_or_create_token
from geonode.security.registry import permissions_registry
from geonode.groups.conf import settings as groups_settings
from geonode.security.permissions import _to_extended_perms

from geonode.base.models import Configuration, UserGeoLimit, GroupGeoLimit
from geonode.base.populate_test_data import (
Expand Down Expand Up @@ -3793,3 +3799,30 @@ def test_configuration_read_only_change_clears_permissions_cache(self):
finally:
config.read_only = original_read_only
config.save()


class TestDefaultSpecialGroupsPermissionsHandler(GeoNodeBaseTestSupport):
@override_settings(DEFAULT_ANONYMOUS_PERMISSIONS="view", DEFAULT_REGISTERED_MEMBERS_PERMISSIONS="download")
def test_handler_sets_default_groups_on_create(self):
resource = create_single_dataset("test_default_special_groups")
handler = DefaultSpecialGroupsPermissionsHandler()
perms_payload = {"users": {}, "groups": {}}

updated = handler.fixup_perms(resource, perms_payload, created=True)

anonymous_group = Group.objects.get(name="anonymous")
registered_group, _ = Group.objects.get_or_create(name=groups_settings.REGISTERED_MEMBERS_GROUP_NAME)
expected_anonymous = _to_extended_perms("view", resource.resource_type, resource.subtype)
expected_registered = _to_extended_perms("download", resource.resource_type, resource.subtype)

self.assertSetEqual(set(updated["groups"][anonymous_group]), set(expected_anonymous))
self.assertSetEqual(set(updated["groups"][registered_group]), set(expected_registered))

def test_handler_skips_when_not_created(self):
resource = create_single_dataset("test_default_special_groups_skip")
handler = DefaultSpecialGroupsPermissionsHandler()
perms_payload = {"users": {}, "groups": {}}

updated = handler.fixup_perms(resource, perms_payload, created=False)

self.assertDictEqual(perms_payload, updated)
7 changes: 5 additions & 2 deletions geonode/security/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
from geonode.groups.models import GroupProfile
from geonode.security.registry import permissions_registry
from geonode.security.permissions import (
get_default_anonymous_compact_permission,
VIEW_RIGHTS,
DOWNLOAD_RIGHTS,
PermSpecCompact,
EDIT_PERMISSIONS,
VIEW_PERMISSIONS,
Expand Down Expand Up @@ -165,11 +168,11 @@ def get_user_visible_groups(user, include_public_invite: bool = False):
class AdvancedSecurityWorkflowManager:
@staticmethod
def is_anonymous_can_view():
return settings.DEFAULT_ANONYMOUS_VIEW_PERMISSION
return get_default_anonymous_compact_permission() in (VIEW_RIGHTS, DOWNLOAD_RIGHTS)

@staticmethod
def is_anonymous_can_download():
return settings.DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION
return get_default_anonymous_compact_permission() == DOWNLOAD_RIGHTS

@staticmethod
def is_group_private_mode():
Expand Down
Loading
Loading