Skip to content

Commit

Permalink
Merge pull request #5908 from GeoNode/ISSUE_5835
Browse files Browse the repository at this point in the history
[Fixes #5835] GNIP-74: Geospatial security restrictions to layers
  • Loading branch information
Alessio Fabiani authored Mar 26, 2020
2 parents 140f89e + 1c03d18 commit 3e07b70
Show file tree
Hide file tree
Showing 64 changed files with 12,529 additions and 13,952 deletions.
4 changes: 4 additions & 0 deletions geonode/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ class GroupProfileResource(ModelResource):
)
member_count = fields.CharField()
manager_count = fields.CharField()
logo_url = fields.CharField()
detail_url = fields.CharField()

class Meta:
Expand All @@ -375,6 +376,9 @@ def dehydrate_detail_url(self, bundle):
"""Return relative URL to the geonode UI's page on the group"""
return reverse('group_detail', args=[bundle.obj.slug])

def dehydrate_logo_url(self, bundle):
return bundle.obj.logo_url


class GroupResource(ModelResource):
group_profile = fields.ToOneField(
Expand Down
29 changes: 16 additions & 13 deletions geonode/api/resourcebase_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,22 @@ def filter_h_keywords(self, queryset, keywords):
filtered = queryset.filter(Q(keywords__in=treeqs))
return filtered

def filter_bbox(self, queryset, bbox):
"""
modify the queryset q to limit to data that intersects with the
provided bbox
bbox - 4 tuple of floats representing 'southwest_lng,southwest_lat,
northeast_lng,northeast_lat'
returns the modified query
"""
bbox = bbox.split(',') # TODO: Why is this different when done through haystack?
bbox = list(map(str, bbox)) # 2.6 compat - float to decimal conversion
intersects = ~(Q(bbox_x0__gt=bbox[2]) | Q(bbox_x1__lt=bbox[0]) |
Q(bbox_y0__gt=bbox[3]) | Q(bbox_y1__lt=bbox[1]))
def filter_bbox(self, queryset, extent_filter):
from geonode.utils import bbox_to_projection
bbox = extent_filter.split(',')
bbox = list(map(str, bbox))

intersects = (Q(bbox_x0__gte=bbox[0]) & Q(bbox_x1__lte=bbox[2]) &
Q(bbox_y0__gte=bbox[1]) & Q(bbox_y1__lte=bbox[3]))

for proj in Layer.objects.order_by('srid').values('srid').distinct():
if proj['srid'] != 'EPSG:4326':
proj_bbox = bbox_to_projection(bbox + ['4326', ],
target_srid=int(proj['srid'][5:]))

if proj_bbox[-1] != 4326:
intersects = intersects | (Q(bbox_x0__gte=proj_bbox[0]) & Q(bbox_x1__lte=proj_bbox[2]) & Q(
bbox_y0__gte=proj_bbox[1]) & Q(bbox_y1__lte=proj_bbox[3]))

return queryset.filter(intersects)

Expand Down
45 changes: 45 additions & 0 deletions geonode/base/migrations/0038_auto_20200318_0953.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 2.2.11 on 2020-03-18 09:53

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('groups', '0032_merge_20200306_1153'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('base', '0037_auto_20200305_1520'),
]

operations = [
migrations.CreateModel(
name='UserGeoLimit',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('wkt', models.TextField(blank=True, db_column='wkt')),
('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.ResourceBase')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='GroupGeoLimit',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('wkt', models.TextField(blank=True, db_column='wkt')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='groups.GroupProfile')),
('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.ResourceBase')),
],
),
migrations.AddField(
model_name='resourcebase',
name='groups_geolimits',
field=models.ManyToManyField(blank=True, null=True, related_name='groups_geolimits', to='base.GroupGeoLimit'),
),
migrations.AddField(
model_name='resourcebase',
name='users_geolimits',
field=models.ManyToManyField(blank=True, null=True, related_name='users_geolimits', to='base.UserGeoLimit'),
),
]
14 changes: 14 additions & 0 deletions geonode/base/migrations/0041_merge_20200323_1119.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 2.2.11 on 2020-03-23 11:19

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('base', '0038_auto_20200318_0953'),
('base', '0040_merge_20200321_2245'),
]

operations = [
]
168 changes: 125 additions & 43 deletions geonode/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,35 @@
import traceback

from django.db import models
from django.conf import settings
from django.core import serializers
from django.utils.html import escape
from django.utils.timezone import now
from django.db.models import Q, signals
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.conf import settings
from django.contrib.staticfiles.templatetags import staticfiles
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.files.storage import default_storage as storage
from django.core.files.base import ContentFile
from django.contrib.auth import get_user_model
from django.contrib.gis.geos import GEOSGeometry
from django.utils.timezone import now
from django.utils.html import escape
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.staticfiles.templatetags import staticfiles
from django.core.files.storage import default_storage as storage

from mptt.models import MPTTModel, TreeForeignKey

from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill

from polymorphic.models import PolymorphicModel
from polymorphic.managers import PolymorphicManager
from pinax.ratings.models import OverallRating
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill

from taggit.models import TagBase, ItemBase
from taggit.managers import TaggableManager, _TaggableManager

from guardian.shortcuts import get_anonymous_user, get_objects_for_user
from treebeard.mp_tree import MP_Node, MP_NodeQuerySet, MP_NodeManager

from geonode.singleton import SingletonModel
from geonode.base.enumerations import (
Expand All @@ -61,10 +68,9 @@
add_url_params,
bbox_to_wkt,
forward_mercator)
from geonode.groups.models import GroupProfile
from geonode.security.models import PermissionLevelMixin
from taggit.managers import TaggableManager, _TaggableManager
from taggit.models import TagBase, ItemBase
from treebeard.mp_tree import MP_Node, MP_NodeQuerySet, MP_NodeManager
from geonode.security.utils import get_visible_resources

from geonode.people.enumerations import ROLE_VALUES

Expand Down Expand Up @@ -323,43 +329,69 @@ class HierarchicalKeyword(TagBase, MP_Node):
objects = HierarchicalKeywordManager()

@classmethod
def dump_bulk_tree(cls, parent=None, keep_ids=True):
def dump_bulk_tree(cls, user, parent=None, keep_ids=True, type=None):
"""Dumps a tree branch to a python data structure."""
user = user or get_anonymous_user()
ctype_filter = [type, ] if type else ['layer', 'map', 'document']
qset = cls._get_serializable_model().get_tree(parent)
if settings.SKIP_PERMS_FILTER:
resources = ResourceBase.objects.all()
else:
resources = get_objects_for_user(
user,
'base.view_resourcebase'
)
resources = resources.filter(
polymorphic_ctype__model__in=ctype_filter,
)
resources = get_visible_resources(
resources,
user,
admin_approval_required=settings.ADMIN_MODERATE_UPLOADS,
unpublished_not_visible=settings.RESOURCE_PUBLISHING,
private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES)
ret, lnk = [], {}
try:
for pyobj in qset:
for pyobj in qset.order_by('name'):
serobj = serializers.serialize('python', [pyobj])[0]
# django's serializer stores the attributes in 'fields'
fields = serobj['fields']
depth = fields['depth'] or 1
fields['text'] = fields['name']
fields['href'] = fields['slug']
del fields['name']
del fields['slug']
del fields['path']
del fields['numchild']
del fields['depth']
if 'id' in fields:
# this happens immediately after a load_bulk
del fields['id']

newobj = {}
for field in fields:
newobj[field] = fields[field]
if keep_ids:
newobj['id'] = serobj['pk']

if (not parent and depth == 1) or\
(parent and depth == parent.depth):
ret.append(newobj)
else:
parentobj = pyobj.get_parent()
parentser = lnk[parentobj.pk]
if 'nodes' not in parentser:
parentser['nodes'] = []
parentser['nodes'].append(newobj)
lnk[pyobj.pk] = newobj
tags_count = 0
try:
tags_count = TaggedContentItem.objects.filter(
content_object__in=resources,
tag=HierarchicalKeyword.objects.get(slug=fields['slug'])).count()
except Exception:
pass
if tags_count > 0:
fields['text'] = fields['name']
fields['href'] = fields['slug']
fields['tags'] = [tags_count]
del fields['name']
del fields['slug']
del fields['path']
del fields['numchild']
del fields['depth']
if 'id' in fields:
# this happens immediately after a load_bulk
del fields['id']
newobj = {}
for field in fields:
newobj[field] = fields[field]
if keep_ids:
newobj['id'] = serobj['pk']

if (not parent and depth == 1) or\
(parent and depth == parent.depth):
ret.append(newobj)
else:
parentobj = pyobj.get_parent()
parentser = lnk[parentobj.pk]
if 'nodes' not in parentser:
parentser['nodes'] = []
parentser['nodes'].append(newobj)
lnk[pyobj.pk] = newobj
except Exception:
pass
return ret
Expand Down Expand Up @@ -785,6 +817,18 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase):
default=False,
help_text=_('Security Rules Are Not Synched with GeoServer!'))

users_geolimits = models.ManyToManyField(
"UserGeoLimit",
related_name="users_geolimits",
null=True,
blank=True)

groups_geolimits = models.ManyToManyField(
"GroupGeoLimit",
related_name="groups_geolimits",
null=True,
blank=True)

def __str__(self):
return "{0}".format(self.title)

Expand Down Expand Up @@ -857,6 +901,12 @@ def ll_bbox(self):
llbbox[3], # y1
self.srid]

@property
def ll_bbox_string(self):
"""WGS84 BBOX is in the format: [x0,y0,x1,y1]."""
return ",".join([str(self.ll_bbox[0]), str(self.ll_bbox[2]),
str(self.ll_bbox[1]), str(self.ll_bbox[3])])

@property
def bbox_string(self):
"""BBOX is in the format: [x0,y0,x1,y1]."""
Expand Down Expand Up @@ -1560,6 +1610,38 @@ def __str__(self):
return 'Configuration'


class UserGeoLimit(models.Model):
user = models.ForeignKey(
get_user_model(),
null=False,
blank=False,
on_delete=models.CASCADE)
resource = models.ForeignKey(
ResourceBase,
null=False,
blank=False,
on_delete=models.CASCADE)
wkt = models.TextField(
db_column='wkt',
blank=True)


class GroupGeoLimit(models.Model):
group = models.ForeignKey(
GroupProfile,
null=False,
blank=False,
on_delete=models.CASCADE)
resource = models.ForeignKey(
ResourceBase,
null=False,
blank=False,
on_delete=models.CASCADE)
wkt = models.TextField(
db_column='wkt',
blank=True)


def resourcebase_post_save(instance, *args, **kwargs):
"""
Used to fill any additional fields after the save.
Expand Down
19 changes: 14 additions & 5 deletions geonode/base/templatetags/base_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,20 @@ def facets(context):
private_groups_not_visibile=settings.GROUP_PRIVATE_RESOURCES)

if extent_filter:
bbox = extent_filter.split(
',') # TODO: Why is this different when done through haystack?
bbox = list(map(str, bbox)) # 2.6 compat - float to decimal conversion
intersects = ~(Q(bbox_x0__gt=bbox[2]) | Q(bbox_x1__lt=bbox[0]) |
Q(bbox_y0__gt=bbox[3]) | Q(bbox_y1__lt=bbox[1]))
from geonode.utils import bbox_to_projection
bbox = extent_filter.split(',')
bbox = list(map(str, bbox))

intersects = (Q(bbox_x0__gt=bbox[0]) & Q(bbox_x1__lt=bbox[2]) &
Q(bbox_y0__gt=bbox[1]) & Q(bbox_y1__lt=bbox[3]))

for proj in Layer.objects.order_by('srid').values('srid').distinct():
if proj['srid'] != 'EPSG:4326':
proj_bbox = bbox_to_projection(bbox + ['4326', ],
target_srid=int(proj['srid'][5:]))
if proj_bbox[-1] != 4326:
intersects = intersects | (Q(bbox_x0__gt=proj_bbox[0]) & Q(bbox_x1__lt=proj_bbox[2]) & Q(
bbox_y0__gt=proj_bbox[1]) & Q(bbox_y1__lt=proj_bbox[3]))

layers = layers.filter(intersects)

Expand Down
2 changes: 0 additions & 2 deletions geonode/base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,10 @@ def thumbnail_upload(
request,
res_id,
template='base/thumbnail_upload.html'):

try:
res = resolve_object(
request, ResourceBase, {
'id': res_id}, 'base.change_resourcebase')

except PermissionDenied:
return HttpResponse(
'You are not allowed to change permissions for this resource',
Expand Down
Loading

0 comments on commit 3e07b70

Please sign in to comment.