Skip to content

Commit

Permalink
[Backport 3.3.x] [Fixes #9013] Advanced Workflow: workflow logic shou…
Browse files Browse the repository at this point in the history
…ld take precedence over standard permissions (#9023)

* [Backport 3.3.x] [Fixes #9013] Advanced Workflow: workflow logic should take precedence over standard permissions

* [Backport 3.3.x] [Fixes #9013] Advanced Workflow: workflow logic should take precedence over standard permissions

* [Backport 3.3.x] [Fixes #9013] Advanced Workflow: workflow logic should take precedence over standard permissions

* [Backport 3.3.x] [Fixes #9013] Advanced Workflow: workflow logic should take precedence over standard permissions

* [CircleCI] Security tests

* [CircleCI] Security tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* [CircleCI] Fix tests

* - Fix issues accordingly to the review

* [CircleCI] Fix tests
  • Loading branch information
Alessio Fabiani authored Apr 6, 2022
1 parent 4aa6fe1 commit 9f98bba
Show file tree
Hide file tree
Showing 62 changed files with 4,136 additions and 2,541 deletions.
22 changes: 21 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,27 @@ workflows:
name: geonode_test_suite
load_docker_cache: false
save_docker_cache: false
test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS if '\''security'\'' not in a and '\''geoserver'\'' not in a]))") geonode.thumbs.tests
test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS if '\''security'\'' not in a and '\''geoserver'\'' not in a and '\''layers'\'' not in a and '\''maps'\'' not in a and '\''documents'\'' not in a and and '\''geoapps'\'' not in a]))") geonode.thumbs.tests
- build:
name: geonode_test_suite_layers
load_docker_cache: false
save_docker_cache: false
test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS if '\''layers'\'' in a]))")
- build:
name: geonode_test_suite_maps
load_docker_cache: false
save_docker_cache: false
test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS if '\''maps'\'' in a]))")
- build:
name: geonode_test_suite_documents
load_docker_cache: false
save_docker_cache: false
test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS if '\''documents'\'' in a]))")
- build:
name: geonode_test_suite_geoapps
load_docker_cache: false
save_docker_cache: false
test_suite: ./test.sh $(python -c "import sys;from geonode import settings;sys.stdout.write('\'' '\''.join([a+'\''.tests'\'' for a in settings.GEONODE_APPS if '\''geoapps'\'' in a]))")
- build:
name: geonode_test_security
load_docker_cache: false
Expand Down
4 changes: 2 additions & 2 deletions geonode/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_layers_get_list_unauth_some_public(self):
client is not logged in
"""

layer = Layer.objects.all()[0]
layer = Layer.objects.first()
layer.set_permissions(self.perm_spec)

resp = self.api_client.get(self.list_url)
Expand Down Expand Up @@ -245,7 +245,7 @@ def test_outh_token(self):
self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7)

perm_spec = {"users": {"admin": ['view_resourcebase']}, "groups": {}}
layer = Layer.objects.all()[0]
layer = Layer.objects.first()
layer.set_permissions(perm_spec)
resp = self.api_client.get(self.list_url)
self.assertGreaterEqual(len(self.deserialize(resp)['objects']), 7)
Expand Down
14 changes: 9 additions & 5 deletions geonode/base/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
GroupCategory,
GroupProfile)

from geonode.base.utils import build_absolute_uri
from geonode.utils import build_absolute_uri
from geonode.security.utils import get_resources_with_perms
from geonode.base.models import Link

Expand Down Expand Up @@ -249,8 +249,12 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)

def get_attribute(self, instance):
_instance = instance.get_real_instance()
if hasattr(_instance, 'embed_url') and _instance.embed_url != NotImplemented:
try:
_instance = instance.get_real_instance()
except Exception as e:
logger.exception(e)
_instance = None
if _instance and hasattr(_instance, 'embed_url') and _instance.embed_url != NotImplemented:
return build_absolute_uri(_instance.embed_url)
else:
return ""
Expand Down Expand Up @@ -307,7 +311,7 @@ class Meta:
model = get_user_model()
name = 'user'
view_name = 'users-list'
fields = ('pk', 'username', 'first_name', 'last_name', 'avatar', 'perms')
fields = ('pk', 'username', 'first_name', 'last_name', 'avatar', 'perms', 'is_superuser', 'is_staff')

@classmethod
def setup_eager_loading(cls, queryset):
Expand Down Expand Up @@ -411,7 +415,7 @@ class Meta:
'pk', 'uuid', 'resource_type', 'polymorphic_ctype_id', 'perms',
'owner', 'poc', 'metadata_author',
'keywords', 'tkeywords', 'regions', 'category',
'title', 'abstract', 'attribution', 'doi', 'alternate', 'bbox_polygon', 'll_bbox_polygon', 'srid',
'title', 'abstract', 'attribution', 'alternate', 'doi', 'bbox_polygon', 'll_bbox_polygon', 'srid',
'date', 'date_type', 'edition', 'purpose', 'maintenance_frequency',
'restriction_code_type', 'constraints_other', 'license', 'language',
'spatial_representation_type', 'temporal_extent_start', 'temporal_extent_end',
Expand Down
62 changes: 37 additions & 25 deletions geonode/base/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################

import sys
import logging

Expand All @@ -28,9 +27,9 @@

from django.urls import reverse
from django.core.files import File
from django.conf.urls import url
from django.contrib.auth import get_user_model
from rest_framework.test import APITestCase, URLPatternsTestCase

from rest_framework.test import APITestCase

from guardian.shortcuts import get_anonymous_user

Expand All @@ -41,7 +40,7 @@
from geonode.favorite.models import Favorite
from geonode.documents.models import Document
from geonode.groups.models import GroupProfile
from geonode.base.utils import build_absolute_uri
from geonode.utils import build_absolute_uri
from geonode.thumbs.exceptions import ThumbnailError
from geonode.base.populate_test_data import create_models, create_single_layer
from geonode.security.utils import get_resources_with_perms
Expand All @@ -61,7 +60,7 @@
test_image = Image.new('RGBA', size=(50, 50), color=(155, 0, 0))


class BaseApiTests(APITestCase, URLPatternsTestCase):
class BaseApiTests(APITestCase):

fixtures = [
'initial_data.json',
Expand All @@ -70,18 +69,8 @@ class BaseApiTests(APITestCase, URLPatternsTestCase):
"test_thesaurus.json"
]

from geonode.urls import urlpatterns

if check_ogc_backend(geoserver.BACKEND_PACKAGE):
from geonode.geoserver.views import layer_acls, resolve_user
urlpatterns += [
url(r'^acls/?$', layer_acls, name='layer_acls'),
url(r'^acls_dep/?$', layer_acls, name='layer_acls_dep'),
url(r'^resolve_user/?$', resolve_user, name='layer_resolve_user'),
url(r'^resolve_user_dep/?$', resolve_user, name='layer_resolve_user_dep'),
]

def setUp(self):
self.maxDiff = None
create_models(b'document')
create_models(b'map')
create_models(b'layer')
Expand All @@ -102,8 +91,9 @@ def test_groups_list(self):
self.assertEqual(response.status_code, 200)
logger.debug(response.data)
self.assertEqual(len(response.data), 5)
self.assertEqual(response.data['total'], 7)
self.assertEqual(len(response.data['group_profiles']), 7)
self.assertEqual(response.data['total'], 4)
self.assertEqual(len(response.data['group_profiles']), 4)
self.assertTrue(all([_g['access'] != 'private' for _g in response.data['group_profiles']]))

# Admin can access all groups
self.assertTrue(self.client.login(username='admin', password='admin'))
Expand All @@ -123,8 +113,8 @@ def test_groups_list(self):
self.assertEqual(response.status_code, 200)
logger.debug(response.data)
self.assertEqual(len(response.data), 5)
self.assertEqual(response.data['total'], 7)
self.assertEqual(len(response.data['group_profiles']), 7)
self.assertEqual(response.data['total'], 6)
self.assertEqual(len(response.data['group_profiles']), 6)
self.assertTrue(any([_g['slug'] == 'priv_1' for _g in response.data['group_profiles']]))

url = reverse('group-profiles-detail', kwargs={'pk': priv_1.pk})
Expand Down Expand Up @@ -469,6 +459,7 @@ def test_base_resources(self):

# Check user permissions
resource = ResourceBase.objects.filter(owner__username='bobby').first()
self.assertEqual(resource.owner.username, 'bobby')
# Admin
response = self.client.get(f"{url}/{resource.id}/", format='json')
self.assertTrue('change_resourcebase' in list(response.data['resource']['perms']))
Expand Down Expand Up @@ -752,7 +743,7 @@ def test_perms_resources(self):
# Admin
self.assertTrue(self.client.login(username='admin', password='admin'))

resource = ResourceBase.objects.filter(owner__username='bobby').first()
resource = ResourceBase.objects.filter(owner__username='bobby', resource_type='layer').first()
set_perms_url = urljoin(f"{reverse('base-resources-detail', kwargs={'pk': resource.pk})}/", 'set_perms/')
get_perms_url = urljoin(f"{reverse('base-resources-detail', kwargs={'pk': resource.pk})}/", 'get_perms/')

Expand All @@ -764,8 +755,8 @@ def test_perms_resources(self):
response = self.client.get(get_perms_url, format='json')
self.assertEqual(response.status_code, 200)
resource_perm_spec = response.data
self.assertTrue('bobby' in resource_perm_spec['users'])
self.assertFalse('norman' in resource_perm_spec['users'])
self.assertTrue('bobby' in resource_perm_spec['users'], resource_perm_spec)
self.assertFalse('norman' in resource_perm_spec['users'], resource_perm_spec)

# Add perms to Norman
resource_perm_spec['users']['norman'] = resource_perm_spec['users']['bobby']
Expand Down Expand Up @@ -838,7 +829,8 @@ def test_resource_types(self):
"""
url = urljoin(f"{reverse('base-resources-list')}/", 'resource_types/')
response = self.client.get(url, format='json')
r_type_names = [item['name'] for item in response.data['resource_types']]
r_types = [item for item in response.data['resource_types']]
r_type_names = [r_type['name'] for r_type in r_types]
self.assertEqual(response.status_code, 200)
self.assertTrue('resource_types' in response.data)
self.assertTrue('layer' in r_type_names)
Expand Down Expand Up @@ -1011,6 +1003,11 @@ def test_owners_list(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['total'], 8)

# Owners Filtering
response = self.client.get(f"{url}?filter{{username.icontains}}=bobby", format='json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['total'], 1)

def test_categories_list(self):
"""
Ensure we can access the list of categories.
Expand All @@ -1035,6 +1032,11 @@ def test_categories_list(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['total'], TopicCategory.objects.count())

# Categories Filtering
response = self.client.get(f"{url}?filter{{identifier.icontains}}=biota", format='json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['total'], 1)

def test_regions_list(self):
"""
Ensure we can access the list of regions.
Expand All @@ -1059,6 +1061,11 @@ def test_regions_list(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['total'], Region.objects.count())

# Regions Filtering
response = self.client.get(f"{url}?filter{{name.icontains}}=Africa", format='json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['total'], 8)

def test_keywords_list(self):
"""
Ensure we can access the list of keywords.
Expand All @@ -1083,6 +1090,11 @@ def test_keywords_list(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['total'], HierarchicalKeyword.objects.count())

# Keywords Filtering
response = self.client.get(f"{url}?filter{{name.icontains}}=Africa", format='json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['total'], 0)

def test_tkeywords_list(self):
"""
Ensure we can access the list of thasaurus keywords.
Expand Down Expand Up @@ -1203,7 +1215,7 @@ def test_set_thumbnail_from_bbox_from_logged_user_for_existing_dataset_raise_exp
Given a logged User and an existing dataset, should raise a ThumbnailException.
"""
# Admin
self.client.login(username="admin", password="admin")
self.assertTrue(self.client.login(username='admin', password='admin'))
dataset_id = Layer.objects.first().resourcebase_ptr_id
url = reverse('base-resources-set-thumb-from-bbox', args=[dataset_id])
payload = {
Expand Down
30 changes: 21 additions & 9 deletions geonode/base/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from geonode.security.utils import (
get_geoapp_subtypes,
get_visible_resources,
get_user_visible_groups,
get_resources_with_perms)

from guardian.shortcuts import get_objects_for_user
Expand Down Expand Up @@ -138,7 +139,17 @@ class GroupViewSet(DynamicModelViewSet):
]
serializer_class = GroupProfileSerializer
pagination_class = GeoNodeApiPagination
queryset = GroupProfile.objects.all()

def get_queryset(self):
"""
Filters the public groups and private ones the current user is member of.
"""
metadata_author_groups = get_user_visible_groups(
self.request.user, include_public_invite=True)
if not isinstance(metadata_author_groups, list):
metadata_author_groups = list(metadata_author_groups.all())
queryset = GroupProfile.objects.filter(id__in=[_g.id for _g in metadata_author_groups])
return queryset.order_by("title")

@extend_schema(methods=['get'], responses={200: UserSerializer(many=True)},
description="API endpoint allowing to retrieve the Group members.")
Expand Down Expand Up @@ -367,24 +378,26 @@ def resource_types(self, request):
})
return Response({"resource_types": resource_types})

@extend_schema(methods=['get'], responses={200: PermSpecSerialiazer()},
@extend_schema(methods=['get', 'put', 'patch', 'delete'],
request=PermSpecSerialiazer(),
responses={200: None},
description="""
Gets an object's the permission levels based on the perm_spec JSON.
Sets an object's the permission levels based on the perm_spec JSON.
the mapping looks like:
```
{
'users': [
'users': {
'AnonymousUser': ['view'],
<username>: ['perm1','perm2','perm3'],
<username2>: ['perm1','perm2','perm3']
...
],
'groups': [
},
'groups': {
<groupname>: ['perm1','perm2','perm3'],
<groupname2>: ['perm1','perm2','perm3'],
...
]
}
}
```
""")
Expand Down Expand Up @@ -444,8 +457,7 @@ def set_perms(self, request, pk=None):
methods=["post"],
permission_classes=[
IsAuthenticated,
],
)
])
def set_thumbnail_from_bbox(self, request, resource_id):
import traceback
from django.utils.datastructures import MultiValueDictKeyError
Expand Down
13 changes: 9 additions & 4 deletions geonode/base/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################
import json
import re
import html
import json
import logging
from django.db.models.query import QuerySet
from bootstrap3_datetime.widgets import DateTimePicker
Expand Down Expand Up @@ -483,16 +483,21 @@ class ResourceBaseForm(TranslationModelForm):
required=False,
widget=forms.Textarea,
help_text=_('Additional metadata, must be in format [\
{"metadata_key": "metadata_value"},\
{"metadata_key": "metadata_value"} \
]')
{"metadata_key": "metadata_value"},\
{"metadata_key": "metadata_value"} \
]')
)

def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)

if self.instance and self.instance.id and self.instance.metadata.exists():
self.fields['extra_metadata'].initial = [x.metadata for x in self.instance.metadata.all()]

for field in self.fields:
if field == 'featured' and self.user and not self.user.is_superuser:
self.fields[field].disabled = True
help_text = self.fields[field].help_text
if help_text != '':
self.fields[field].widget.attrs.update(
Expand Down
18 changes: 18 additions & 0 deletions geonode/base/migrations/0068_auto_20220403_1334.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.24 on 2022-04-03 13:34

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('base', '0067_auto_20220401_1155'),
]

operations = [
migrations.AlterField(
model_name='resourcebase',
name='uuid',
field=models.CharField(max_length=36, unique=True),
),
]
Loading

0 comments on commit 9f98bba

Please sign in to comment.