#########################################################################
#
# Copyright (C) 2021 OSGeo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################
import os
import copy
import typing
import logging
from uuid import uuid1, uuid4
from abc import ABCMeta, abstractmethod
from guardian.models import UserObjectPermission, GroupObjectPermission
from guardian.shortcuts import assign_perm, get_anonymous_user
from django.conf import settings
from django.db import transaction
from django.db.models.query import QuerySet
from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.utils.module_loading import import_string
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError, FieldDoesNotExist
from geonode.thumbs.thumbnails import _generate_thumbnail_name
from geonode.documents.tasks import create_document_thumbnail
from geonode.security.permissions import PermSpecCompact, DATA_STYLABLE_RESOURCES_SUBTYPES
from geonode.security.utils import perms_as_set, get_user_groups, skip_registered_members_common_group
from . import settings as rm_settings
from .utils import update_resource, resourcebase_post_save
from ..base import enumerations
from ..base.models import ResourceBase, LinkedResource
from ..security.utils import AdvancedSecurityWorkflowManager
from ..layers.metadata import parse_metadata
from ..documents.models import Document
from ..layers.models import Dataset, Attribute
from ..maps.models import Map
from ..storage.manager import storage_manager
[docs]
logger = logging.getLogger(__name__)
[docs]
class ResourceManagerInterface(metaclass=ABCMeta):
@abstractmethod
[docs]
def search(self, filter: dict, /, resource_type: typing.Optional[object]) -> QuerySet:
"""Returns a QuerySet of the filtered resources into the DB.
- The 'filter' parameter should be an dictionary with the filtering criteria;
- 'filter' = None won't return any result
- 'filter' = {} will return the whole set
- The 'resource_type' parameter allows to specify the concrete resource model (e.g. Dataset, Document, Map, ...)
- 'resource_type' must be a class
- 'resource_type' = Dataset will return a set of the only available Layers
"""
pass
@abstractmethod
[docs]
def exists(self, uuid: str, /, instance: ResourceBase = None) -> bool:
"""Returns 'True' or 'False' if the resource exists or not.
- If 'instance' is provided, it will take precedence on 'uuid'
- The existance criteria might be subject to the 'concrete resource manager' one, dependent on the resource type
e.g.: a local Dataset existance check will be constrained by the existance of the layer on the GIS backend
"""
pass
@abstractmethod
[docs]
def delete(self, uuid: str, /, instance: ResourceBase = None) -> int:
"""Deletes a resource from the DB.
- If 'instance' is provided, it will take precedence on 'uuid'
- It will also fallback to the 'concrete resource manager' delete model.
- This will eventually delete the related resources on the GIS backend too.
"""
pass
@abstractmethod
[docs]
def create(self, uuid: str, /, resource_type: typing.Optional[object] = None, defaults: dict = {}) -> ResourceBase:
"""The method will just create a new 'resource_type' on the DB model and invoke the 'post save' triggers.
- It assumes any GIS backend resource (e.g. layers on GeoServer) already exist.
- It is possible to pass initial default values, like the 'files' from the 'storage_manager' trhgouh the 'defaults' dictionary
"""
pass
@abstractmethod
[docs]
def update(
self,
uuid: str,
/,
instance: ResourceBase = None,
xml_file: str = None,
metadata_uploaded: bool = False,
vals: dict = {},
regions: dict = {},
keywords: dict = {},
custom: dict = {},
notify: bool = True,
) -> ResourceBase:
"""The method will update an existing 'resource_type' on the DB model and invoke the 'post save' triggers.
- It assumes any GIS backend resource (e.g. layers on GeoServer) already exist.
- It is possible to pass initial default values, like the 'files' from the 'storage_manager' trhgouh the 'vals' dictionary
- The 'xml_file' parameter allows to fetch metadata values from a file
- The 'notify' parameter allows to notify the members that the resource has been updated
"""
pass
@abstractmethod
[docs]
def ingest(
self,
files: typing.List[str],
/,
uuid: str = None,
resource_type: typing.Optional[object] = None,
defaults: dict = {},
**kwargs,
) -> ResourceBase:
"""The method allows to create a resource by providing the list of files.
e.g.:
In [1]: from geonode.resource.manager import resource_manager
In [2]: from geonode.layers.models import Dataset
In [3]: from django.contrib.auth import get_user_model
In [4]: admin = get_user_model().objects.get(username='admin')
In [5]: files = ["/.../san_andres_y_providencia_administrative.dbf", "/.../san_andres_y_providencia_administrative.prj",
...: "/.../san_andres_y_providencia_administrative.shx", "/.../san_andres_y_providencia_administrative.sld", "/.../san_andres_y_providencia_administrative.shp"]
In [6]: resource_manager.ingest(files, resource_type=Dataset, defaults={'owner': admin})
"""
pass
@abstractmethod
[docs]
def copy(
self, instance: ResourceBase, /, uuid: str = None, owner: settings.AUTH_USER_MODEL = None, defaults: dict = {}
) -> ResourceBase:
"""The method makes a copy of the existing resource.
- It makes a copy of the files
- It creates a new layer on the GIS backend in the case the ResourceType is a Dataset
"""
pass
@abstractmethod
[docs]
def append(self, instance: ResourceBase, vals: dict = {}) -> ResourceBase:
"""The method appends data to an existing resource.
- It assumes any GIS backend resource (e.g. layers on GeoServer) already exist.
"""
pass
@abstractmethod
[docs]
def replace(self, instance: ResourceBase, vals: dict = {}) -> ResourceBase:
"""The method replaces data of an existing resource.
- It assumes any GIS backend resource (e.g. layers on GeoServer) already exist.
"""
pass
@abstractmethod
[docs]
def exec(self, method: str, uuid: str, /, instance: ResourceBase = None, **kwargs) -> ResourceBase:
"""A generic 'exec' method allowing to invoke specific methods of the concrete resource manager not exposed by the interface.
- The parameter 'method' represents the actual name of the concrete method to invoke.
"""
pass
@abstractmethod
[docs]
def remove_permissions(self, uuid: str, /, instance: ResourceBase = None) -> bool:
"""Completely cleans the permissions of a resource, resetting it to the default state (owner only)"""
pass
@abstractmethod
[docs]
def set_permissions(
self,
uuid: str,
/,
instance: ResourceBase = None,
owner: settings.AUTH_USER_MODEL = None,
permissions: dict = {},
created: bool = False,
approval_status_changed: bool = False,
group_status_changed: bool = False,
) -> bool:
"""Sets the permissions of a resource.
- It optionally gets a JSON 'perm_spec' through the 'permissions' parameter
- If no 'perm_spec' is provided, it will set the default permissions (owner only)
"""
pass
@abstractmethod
[docs]
def set_thumbnail(
self, uuid: str, /, instance: ResourceBase = None, overwrite: bool = True, check_bbox: bool = True
) -> bool:
"""Allows to generate or re-generate the Thumbnail of a Resource."""
pass
[docs]
class ResourceManager(ResourceManagerInterface):
def __init__(self, concrete_manager=None):
[docs]
self._concrete_resource_manager = concrete_manager or self._get_concrete_manager()
[docs]
def _get_concrete_manager(self):
return import_string(rm_settings.RESOURCE_MANAGER_CONCRETE_CLASS)()
@classmethod
[docs]
def _get_instance(cls, uuid: str) -> ResourceBase:
return ResourceBase.objects.filter(uuid=uuid).first()
[docs]
def search(self, filter: dict, /, resource_type: typing.Optional[object]) -> QuerySet:
_class = resource_type or ResourceBase
_resources_queryset = _class.objects.filter(**filter)
_filter = self._concrete_resource_manager.search(filter, resource_type=_class)
if _filter:
_resources_queryset.filter(_filter)
return _resources_queryset
[docs]
def exists(self, uuid: str, /, instance: ResourceBase = None) -> bool:
_resource = instance or ResourceManager._get_instance(uuid)
if _resource:
return self._concrete_resource_manager.exists(uuid, instance=_resource)
return False
[docs]
def delete(self, uuid: str, /, instance: ResourceBase = None) -> int:
_resource = instance or ResourceManager._get_instance(uuid)
uuid = uuid or _resource.uuid
if _resource and ResourceBase.objects.filter(uuid=uuid).exists():
try:
_resource.set_processing_state(enumerations.STATE_RUNNING)
try:
if isinstance(_resource.get_real_instance(), Dataset):
"""
- Remove any associated style to the dataset, if it is not used by other datasets.
- Default style will be deleted in post_delete_dataset.
- Remove the dataset from any associated map, if any.
- Remove the dataset default style.
"""
try:
from geonode.maps.models import MapLayer
logger.debug(
"Going to delete associated maplayers for [%s]", _resource.get_real_instance().name
)
MapLayer.objects.filter(
name=_resource.get_real_instance().alternate,
ows_url=_resource.get_real_instance().ows_url,
).delete()
except Exception as e:
logger.exception(e)
try:
from pinax.ratings.models import OverallRating
ct = ContentType.objects.get_for_model(_resource.get_real_instance())
OverallRating.objects.filter(
content_type=ct, object_id=_resource.get_real_instance().id
).delete()
except Exception as e:
logger.exception(e)
# Remove uploaded files, if any
ResourceBase.objects.cleanup_uploaded_files(resource_id=_resource.id)
try:
_resource.get_real_instance().styles.delete()
_resource.get_real_instance().default_style.delete()
except Exception as e:
logger.debug(f"Error occurred while trying to delete the Dataset Styles: {e}")
self.remove_permissions(
_resource.get_real_instance().uuid, instance=_resource.get_real_instance()
)
except Exception as e:
logger.exception(e)
try:
from ..services.models import Service
if _resource.remote_typename and Service.objects.filter(name=_resource.remote_typename).exists():
_service = Service.objects.filter(name=_resource.remote_typename).get()
if _service.harvester:
_service.harvester.harvestable_resources.filter(
geonode_resource__uuid=_resource.get_real_instance().uuid
).update(should_be_harvested=False)
except Exception as e:
logger.exception(e)
self._concrete_resource_manager.delete(uuid, instance=_resource)
try:
_resource.get_real_instance().delete()
except ResourceBase.DoesNotExist:
pass
return 1
except Exception as e:
logger.exception(e)
finally:
ResourceBase.objects.filter(uuid=uuid).delete()
return 0
[docs]
def create(self, uuid: str, /, resource_type: typing.Optional[object] = None, defaults: dict = {}) -> ResourceBase:
if resource_type.objects.filter(uuid=uuid).exists():
return resource_type.objects.filter(uuid=uuid).get()
uuid = uuid or str(uuid4())
_resource, _created = resource_type.objects.get_or_create(uuid=uuid, defaults=defaults)
if _resource and _created:
_resource.set_processing_state(enumerations.STATE_RUNNING)
try:
with transaction.atomic():
_resource.set_missing_info()
_resource = self._concrete_resource_manager.create(
uuid, resource_type=resource_type, defaults=defaults
)
_resource.save()
resourcebase_post_save(_resource.get_real_instance())
_resource.set_processing_state(enumerations.STATE_PROCESSED)
except Exception as e:
logger.exception(e)
self.delete(_resource.uuid, instance=_resource)
raise e
return _resource
[docs]
def update(
self,
uuid: str,
/,
instance: ResourceBase = None,
xml_file: str = None,
metadata_uploaded: bool = False,
vals: dict = {},
regions: list = [],
keywords: list = [],
custom: dict = {},
notify: bool = True,
extra_metadata: list = [],
*args,
**kwargs,
) -> ResourceBase:
_resource = instance or ResourceManager._get_instance(uuid)
if _resource:
_resource.set_processing_state(enumerations.STATE_RUNNING)
_resource.set_missing_info()
_resource.metadata_uploaded = metadata_uploaded
logger.debug(f"Look for xml and finalize Dataset metadata {_resource}")
try:
with transaction.atomic():
if metadata_uploaded and xml_file:
_md_file = None
try:
_md_file = storage_manager.open(xml_file, mode="r")
except Exception as e:
logger.exception(e)
if os.path.exists(xml_file) and os.path.isfile(xml_file):
_md_file = open(xml_file, mode="r")
if _md_file:
_md_file_content = _md_file.read()
_resource.metadata_xml = _md_file_content
_uuid, vals, regions, keywords, custom = parse_metadata(_md_file_content)
if uuid and uuid != _uuid:
raise ValidationError(
"The UUID identifier from the XML Metadata is different from the {_resource} one."
)
else:
uuid = _uuid
logger.debug(f"Update Dataset with information coming from XML File if available {_resource}")
if not kwargs.get("store_spatial_files", True) and vals.get("files", []):
vals.update({"files": []})
_resource.save()
_resource = update_resource(
instance=_resource.get_real_instance(),
regions=regions,
keywords=keywords,
vals=vals,
extra_metadata=extra_metadata,
)
_resource = self._concrete_resource_manager.update(uuid, instance=_resource, notify=notify)
# The following is only a demo proof of concept for a pluggable WF subsystem
from geonode.resource.processing.models import ProcessingWorkflow
_p = ProcessingWorkflow.objects.first()
if _p and _p.is_enabled:
for _task in _p.get_tasks():
_task.execute(_resource)
_resource.set_processing_state(enumerations.STATE_PROCESSED)
except Exception as e:
logger.exception(e)
_resource.set_processing_state(enumerations.STATE_INVALID)
raise
finally:
try:
_resource.save(notify=notify)
resourcebase_post_save(_resource.get_real_instance(), kwargs={**kwargs, **custom})
_resource.set_permissions(
created=False,
approval_status_changed=(
vals is not None and any([x in vals for x in ["is_approved", "is_published"]])
),
group_status_changed=(vals is not None and "group" in vals),
)
if kwargs.get("sld_file", False) and kwargs.get("sld_uploaded", False):
self._concrete_resource_manager.set_style(
method="",
uuid=_resource.uuid,
resource=_resource,
sld_file=kwargs.get("sld_file", False),
sld_uploaded=kwargs.get("sld_uploaded", False),
)
_resource.set_permissions()
if _resource.state != enumerations.STATE_INVALID:
_resource.set_processing_state(enumerations.STATE_PROCESSED)
except Exception as e:
logger.exception(e)
finally:
_resource.clear_dirty_state()
return _resource
[docs]
def ingest(
self,
files: typing.List[str],
/,
uuid: str = None,
resource_type: typing.Optional[object] = None,
defaults: dict = {},
**kwargs,
) -> ResourceBase:
instance = None
to_update = defaults.copy()
if "files" in to_update:
to_update.pop("files")
try:
with transaction.atomic():
if resource_type == Document:
if "name" in to_update:
to_update.pop("name")
if files:
to_update["files"] = storage_manager.copy_files_list(files)
instance = self.create(uuid, resource_type=Document, defaults=to_update)
elif resource_type == Dataset:
if files:
instance = self.create(uuid, resource_type=Dataset, defaults=to_update)
if instance:
instance = self._concrete_resource_manager.ingest(
storage_manager.copy_files_list(files),
uuid=instance.uuid,
resource_type=resource_type,
defaults=to_update,
**kwargs,
)
instance.set_processing_state(enumerations.STATE_PROCESSED)
instance.save(notify=False)
except Exception as e:
logger.exception(e)
if instance:
instance.set_processing_state(enumerations.STATE_INVALID)
if instance:
try:
resourcebase_post_save(instance.get_real_instance())
# Finalize Upload
if "user" in to_update:
to_update.pop("user")
instance = self.update(instance.uuid, instance=instance, vals=to_update)
self.set_thumbnail(instance.uuid, instance=instance)
except Exception as e:
logger.exception(e)
finally:
instance.clear_dirty_state()
return instance
[docs]
def copy(
self, instance: ResourceBase, /, uuid: str = None, owner: settings.AUTH_USER_MODEL = None, defaults: dict = {}
) -> ResourceBase:
_resource = None
if instance:
try:
instance.set_processing_state(enumerations.STATE_RUNNING)
with transaction.atomic():
_resource = copy.copy(instance.get_real_instance())
_resource.owner = owner or instance.get_real_instance().owner
_resource.pk = _resource.id = None
_resource.uuid = uuid or str(uuid4())
try:
# Avoid Integrity errors...
_resource.get_real_instance()._meta.get_field("name")
_name = defaults.get("name", _resource.get_real_instance().name)
_resource.get_real_instance().name = defaults["name"] = f"{_name}_{uuid1().hex[:8]}"
except FieldDoesNotExist:
if "name" in defaults:
defaults.pop("name")
_resource.save()
for lr in LinkedResource.get_linked_resources(source=instance.pk, is_internal=False):
LinkedResource.objects.get_or_create(
source_id=_resource.pk, target_id=lr.target.pk, internal=False
)
for lr in LinkedResource.get_linked_resources(target=instance.pk, is_internal=False):
LinkedResource.objects.get_or_create(
source_id=lr.source.pk, target_id=_resource.pk, internal=False
)
if isinstance(instance.get_real_instance(), Dataset):
for attribute in Attribute.objects.filter(dataset=instance.get_real_instance()):
_attribute = copy.copy(attribute)
_attribute.pk = _attribute.id = None
_attribute.dataset = _resource.get_real_instance()
_attribute.save()
if isinstance(instance.get_real_instance(), Map):
for maplayer in instance.get_real_instance().maplayers.iterator():
_maplayer = copy.copy(maplayer)
_maplayer.pk = _maplayer.id = None
_maplayer.map = _resource.get_real_instance()
_maplayer.save()
to_update = {}
try:
to_update = storage_manager.copy(_resource).copy()
except Exception as e:
logger.exception(e)
_resource = self._concrete_resource_manager.copy(instance, uuid=_resource.uuid, defaults=to_update)
except Exception as e:
logger.exception(e)
_resource = None
finally:
instance.set_processing_state(enumerations.STATE_PROCESSED)
instance.save(notify=False)
if _resource:
try:
to_update.update(defaults)
# Refresh from DB
_resource.refresh_from_db()
return self.update(_resource.uuid, _resource, vals=to_update)
except Exception as e:
logger.exception(e)
finally:
_resource.set_processing_state(enumerations.STATE_PROCESSED)
return _resource
[docs]
def append(self, instance: ResourceBase, vals: dict = {}, *args, **kwargs):
if self._validate_resource(instance.get_real_instance(), "append"):
self._concrete_resource_manager.append(instance.get_real_instance(), vals=vals)
to_update = vals.copy()
if instance:
if "user" in to_update:
to_update.pop("user")
return self.update(instance.uuid, instance.get_real_instance(), vals=to_update, *args, **kwargs)
return instance
[docs]
def replace(self, instance: ResourceBase, vals: dict = {}, *args, **kwargs):
if self._validate_resource(instance.get_real_instance(), "replace"):
if vals.get("files", None) and kwargs.get("store_spatial_files", True):
vals.update(storage_manager.replace(instance.get_real_instance(), vals.get("files")))
self._concrete_resource_manager.replace(instance.get_real_instance(), vals=vals, *args, **kwargs)
to_update = vals.copy()
if instance:
if "user" in to_update:
to_update.pop("user")
return self.update(instance.uuid, instance.get_real_instance(), vals=to_update, *args, **kwargs)
return instance
[docs]
def _validate_resource(self, instance: ResourceBase, action_type: str) -> bool:
if not isinstance(instance, Dataset) and action_type == "append":
raise Exception("Append data is available only for Layers")
if isinstance(instance, Document) and action_type == "replace":
return True
exists = self._concrete_resource_manager.exists(instance.uuid, instance)
if exists and action_type == "append":
if isinstance(instance, Dataset):
if instance.is_vector():
is_valid = True
elif exists and action_type == "replace":
is_valid = True
else:
raise ObjectDoesNotExist("Resource does not exists")
return is_valid
@transaction.atomic
[docs]
def exec(self, method: str, uuid: str, /, instance: ResourceBase = None, **kwargs) -> ResourceBase:
_resource = instance or ResourceManager._get_instance(uuid)
if _resource:
if hasattr(self._concrete_resource_manager, method):
_method = getattr(self._concrete_resource_manager, method)
return _method(method, uuid, instance=_resource, **kwargs)
return instance
[docs]
def remove_permissions(self, uuid: str, /, instance: ResourceBase = None) -> bool:
"""Remove object permissions on given resource.
If is a layer removes the layer specific permissions then the
resourcebase permissions.
"""
_resource = instance or ResourceManager._get_instance(uuid)
if _resource:
_resource.set_processing_state(enumerations.STATE_RUNNING)
try:
with transaction.atomic():
logger.debug(f"Removing all permissions on {_resource}")
from geonode.layers.models import Dataset
_dataset = (
_resource.get_real_instance() if isinstance(_resource.get_real_instance(), Dataset) else None
)
if not _dataset:
try:
_dataset = _resource.dataset if hasattr(_resource, "dataset") else None
except Exception:
_dataset = None
if _dataset:
UserObjectPermission.objects.filter(
content_type=ContentType.objects.get_for_model(_dataset), object_pk=_resource.id
).delete()
GroupObjectPermission.objects.filter(
content_type=ContentType.objects.get_for_model(_dataset), object_pk=_resource.id
).delete()
UserObjectPermission.objects.filter(
content_type=ContentType.objects.get_for_model(_resource.get_self_resource()),
object_pk=_resource.id,
).delete()
GroupObjectPermission.objects.filter(
content_type=ContentType.objects.get_for_model(_resource.get_self_resource()),
object_pk=_resource.id,
).delete()
if not self._concrete_resource_manager.remove_permissions(uuid, instance=_resource):
raise Exception("Could not complete concrete manager operation successfully!")
_resource.set_processing_state(enumerations.STATE_PROCESSED)
return True
except Exception as e:
logger.exception(e)
_resource.set_processing_state(enumerations.STATE_INVALID)
finally:
_resource.clear_dirty_state()
return False
[docs]
def set_permissions(
self,
uuid: str,
/,
instance: ResourceBase = None,
owner: settings.AUTH_USER_MODEL = None,
permissions: dict = {},
created: bool = False,
approval_status_changed: bool = False,
group_status_changed: bool = False,
) -> bool:
_resource = instance or ResourceManager._get_instance(uuid)
if _resource:
_resource = _resource.get_real_instance()
_resource.set_processing_state(enumerations.STATE_RUNNING)
logger.debug(f"Finalizing (permissions and notifications) on resource {instance}")
try:
with transaction.atomic():
logger.debug(f"Setting permissions {permissions} on {_resource}")
# default permissions for owner
if owner and owner != _resource.owner:
_resource.owner = owner
ResourceBase.objects.filter(uuid=_resource.uuid).update(owner=owner)
_owner = _resource.owner
_resource_type = getattr(_resource, "resource_type", None) or _resource.polymorphic_ctype.name
_resource_subtype = (getattr(_resource, "subtype", None) or "").lower()
# default permissions for anonymous users
anonymous_group, _ = Group.objects.get_or_create(name="anonymous")
if not anonymous_group:
raise Exception("Could not acquire 'anonymous' Group.")
# Gathering and validating the current permissions (if any has been passed)
if not created and permissions is None:
permissions = _resource.get_all_level_info()
if permissions:
if PermSpecCompact.validate(permissions):
_permissions = PermSpecCompact(copy.deepcopy(permissions), _resource).extended
else:
_permissions = copy.deepcopy(permissions)
else:
_permissions = None
# Fixup Advanced Workflow permissions
_perm_spec = AdvancedSecurityWorkflowManager.get_permissions(
_resource.uuid,
instance=_resource,
permissions=_permissions,
created=created,
approval_status_changed=approval_status_changed,
group_status_changed=group_status_changed,
)
"""
Cleanup the Guardian tables
"""
self.remove_permissions(uuid, instance=_resource)
def _safe_assign_perm(perm, user_or_group, obj=None):
try:
assign_perm(perm, user_or_group, obj)
except Permission.DoesNotExist as e:
logger.warn(e)
if permissions is not None and len(permissions):
"""
Sets an object's the permission levels based on the perm_spec JSON.
the mapping looks like:
{
'users': {
'AnonymousUser': ['view'],
<username>: ['perm1','perm2','perm3'],
<username2>: ['perm1','perm2','perm3']
...
},
'groups': [
<groupname>: ['perm1','perm2','perm3'],
<groupname2>: ['perm1','perm2','perm3'],
...
]
}
"""
# Anonymous User group
if "users" in _perm_spec and (
"AnonymousUser" in _perm_spec["users"] or get_anonymous_user() in _perm_spec["users"]
):
anonymous_user = (
"AnonymousUser" if "AnonymousUser" in _perm_spec["users"] else get_anonymous_user()
)
perms = copy.deepcopy(_perm_spec["users"][anonymous_user])
_perm_spec["users"].pop(anonymous_user)
_prev_perm = _perm_spec["groups"].get(anonymous_group, []) if "groups" in _perm_spec else []
_perm_spec["groups"][anonymous_group] = set.union(
perms_as_set(_prev_perm), perms_as_set(perms)
)
for perm in _perm_spec["groups"][anonymous_group]:
if _resource_type == "dataset" and perm in (
"change_dataset_data",
"change_dataset_style",
"add_dataset",
"change_dataset",
"delete_dataset",
):
if (
perm == "change_dataset_style"
and _resource_subtype not in DATA_STYLABLE_RESOURCES_SUBTYPES
):
pass
else:
_safe_assign_perm(perm, anonymous_group, _resource.dataset)
elif AdvancedSecurityWorkflowManager.assignable_perm_condition(perm, _resource_type):
_safe_assign_perm(perm, anonymous_group, _resource.get_self_resource())
# All the other users
if "users" in _perm_spec and len(_perm_spec["users"]) > 0:
for user, perms in _perm_spec["users"].items():
_user = get_user_model().objects.get(username=user)
if user != "AnonymousUser" and user != get_anonymous_user():
for perm in perms:
if _resource_type == "dataset" and perm in (
"change_dataset_data",
"change_dataset_style",
"add_dataset",
"change_dataset",
"delete_dataset",
):
if (
perm == "change_dataset_style"
and _resource_subtype not in DATA_STYLABLE_RESOURCES_SUBTYPES
):
pass
else:
_safe_assign_perm(perm, _user, _resource.dataset)
elif AdvancedSecurityWorkflowManager.assignable_perm_condition(
perm, _resource_type
):
_safe_assign_perm(perm, _user, _resource.get_self_resource())
# All the other groups
if "groups" in _perm_spec and len(_perm_spec["groups"]) > 0:
for group, perms in _perm_spec["groups"].items():
_group = Group.objects.get(name=group)
for perm in perms:
if _resource_type == "dataset" and perm in (
"change_dataset_data",
"change_dataset_style",
"add_dataset",
"change_dataset",
"delete_dataset",
):
if (
perm == "change_dataset_style"
and _resource_subtype not in DATA_STYLABLE_RESOURCES_SUBTYPES
):
pass
else:
_safe_assign_perm(perm, _group, _resource.dataset)
elif AdvancedSecurityWorkflowManager.assignable_perm_condition(
perm, _resource_type
):
_safe_assign_perm(perm, _group, _resource.get_self_resource())
# AnonymousUser
if "users" in _perm_spec and len(_perm_spec["users"]) > 0:
if "AnonymousUser" in _perm_spec["users"] or get_anonymous_user() in _perm_spec["users"]:
_user = get_anonymous_user()
anonymous_user = (
"AnonymousUser" if "AnonymousUser" in _perm_spec["users"] else get_anonymous_user()
)
perms = _perm_spec["users"][anonymous_user]
for perm in perms:
if _resource_type == "dataset" and perm in (
"change_dataset_data",
"change_dataset_style",
"add_dataset",
"change_dataset",
"delete_dataset",
):
if (
perm == "change_dataset_style"
and _resource_subtype not in DATA_STYLABLE_RESOURCES_SUBTYPES
):
pass
else:
_safe_assign_perm(perm, _user, _resource.dataset)
elif AdvancedSecurityWorkflowManager.assignable_perm_condition(
perm, _resource_type
):
_safe_assign_perm(perm, _user, _resource.get_self_resource())
else:
# Anonymous
if AdvancedSecurityWorkflowManager.is_anonymous_can_view():
_safe_assign_perm("view_resourcebase", anonymous_group, _resource.get_self_resource())
_prev_perm = _perm_spec["groups"].get(anonymous_group, []) if "groups" in _perm_spec else []
_perm_spec["groups"][anonymous_group] = set.union(
perms_as_set(_prev_perm), perms_as_set("view_resourcebase")
)
else:
for user_group in get_user_groups(_owner):
if not skip_registered_members_common_group(user_group):
_safe_assign_perm("view_resourcebase", user_group, _resource.get_self_resource())
_prev_perm = (
_perm_spec["groups"].get(user_group, []) if "groups" in _perm_spec else []
)
_perm_spec["groups"][user_group] = set.union(
perms_as_set(_prev_perm), perms_as_set("view_resourcebase")
)
if AdvancedSecurityWorkflowManager.assignable_perm_condition(
"download_resourcebase", _resource_type
):
if AdvancedSecurityWorkflowManager.is_anonymous_can_download():
_safe_assign_perm(
"download_resourcebase", anonymous_group, _resource.get_self_resource()
)
_prev_perm = (
_perm_spec["groups"].get(anonymous_group, []) if "groups" in _perm_spec else []
)
_perm_spec["groups"][anonymous_group] = set.union(
perms_as_set(_prev_perm), perms_as_set("download_resourcebase")
)
else:
for user_group in get_user_groups(_owner):
if not skip_registered_members_common_group(user_group):
_safe_assign_perm(
"download_resourcebase", user_group, _resource.get_self_resource()
)
_prev_perm = (
_perm_spec["groups"].get(user_group, []) if "groups" in _perm_spec else []
)
_perm_spec["groups"][user_group] = set.union(
perms_as_set(_prev_perm), perms_as_set("download_resourcebase")
)
if _resource_type == "dataset":
# only for layer owner
_safe_assign_perm("change_dataset_data", _owner, _resource)
_safe_assign_perm("change_dataset_style", _owner, _resource)
_prev_perm = _perm_spec["users"].get(_owner, []) if "users" in _perm_spec else []
_perm_spec["users"][_owner] = set.union(
perms_as_set(_prev_perm), perms_as_set(["change_dataset_data", "change_dataset_style"])
)
_resource = AdvancedSecurityWorkflowManager.handle_moderated_uploads(
_resource.uuid, instance=_resource
)
# Fixup GIS Backend Security Rules Accordingly
if not self._concrete_resource_manager.set_permissions(
uuid,
instance=_resource,
owner=owner,
permissions=_resource.get_all_level_info(),
created=created,
):
# This might not be a severe error. E.g. for datasets outside of local GeoServer
logger.error(Exception("Could not complete concrete manager operation successfully!"))
_resource.set_processing_state(enumerations.STATE_PROCESSED)
return True
except Exception as e:
logger.exception(e)
_resource.set_processing_state(enumerations.STATE_INVALID)
finally:
_resource.clear_dirty_state()
return False
[docs]
def set_thumbnail(
self,
uuid: str,
/,
instance: ResourceBase = None,
overwrite: bool = True,
check_bbox: bool = True,
thumbnail=None,
) -> bool:
_resource = instance or ResourceManager._get_instance(uuid)
if _resource:
try:
with transaction.atomic():
if thumbnail:
file_name = _generate_thumbnail_name(_resource.get_real_instance())
_resource.save_thumbnail(file_name, thumbnail)
else:
if instance and isinstance(instance.get_real_instance(), Document):
if overwrite or not instance.thumbnail_url:
create_document_thumbnail.apply((instance.id,))
self._concrete_resource_manager.set_thumbnail(
uuid, instance=_resource, overwrite=overwrite, check_bbox=check_bbox
)
return True
except Exception as e:
logger.exception(e)
return False
[docs]
resource_manager = ResourceManager()