Skip to content

Commit f9f2008

Browse files
[Fixes #12996] Fix permissions reassign of API to transfer resources
1 parent 03cc34e commit f9f2008

File tree

3 files changed

+70
-17
lines changed

3 files changed

+70
-17
lines changed

geonode/base/api/views.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,9 @@ def resource_service_permissions(self, request, pk, *args, **kwargs):
618618
)
619619
elif request.method == "PUT":
620620
perms_spec_compact = PermSpecCompact(request.data, resource)
621+
if resource.dirty_state:
622+
raise Exception("Cannot update if the resource is in dirty state")
623+
resource.set_dirty_state()
621624
_exec_request = ExecutionRequest.objects.create(
622625
user=request.user,
623626
func_name="set_permissions",
@@ -634,6 +637,9 @@ def resource_service_permissions(self, request, pk, *args, **kwargs):
634637
perms_spec_compact_patch = PermSpecCompact(request.data, resource)
635638
perms_spec_compact_resource = PermSpecCompact(perms_spec.compact, resource)
636639
perms_spec_compact_resource.merge(perms_spec_compact_patch)
640+
if resource.dirty_state:
641+
raise Exception("Cannot update if the resource is in dirty state")
642+
resource.set_dirty_state()
637643
_exec_request = ExecutionRequest.objects.create(
638644
user=request.user,
639645
func_name="set_permissions",

geonode/people/api/views.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
#########################################################################
2+
#
3+
# Copyright (C) 2025 OSGeo
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
#
18+
#########################################################################
19+
20+
import logging
121
from django.conf import settings
222
from dynamic_rest.filters import DynamicFilterBackend, DynamicSortingFilter
323
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
@@ -25,6 +45,9 @@
2545
from geonode.security.registry import permissions_registry
2646

2747

48+
logger = logging.getLogger()
49+
50+
2851
class UserViewSet(DynamicModelViewSet):
2952
"""
3053
API endpoint that allows users to be viewed or edited.
@@ -151,9 +174,16 @@ def transfer_resources(self, request, pk=None):
151174
admin = get_user_model().objects.filter(is_superuser=True, is_staff=True).first()
152175
target_user = request.data.get("newOwner") # the new owner
153176
previous_owner = request.data.get("currentOwner") # the previous owner, usually it match the user
154-
transfer_resource_subset = request.data.get("resources", None)
177+
transfer_resource_subset = (
178+
request.data.get("resources", None)
179+
if not hasattr(request.data, "getlist")
180+
else request.data.getlist("resources", None)
181+
)
155182
target = None
156183

184+
if not target_user and previous_owner:
185+
return Response("Payload not passed", status=400)
186+
157187
if user.is_superuser or (
158188
not user.is_superuser
159189
and ResourceBase.objects.filter(owner=user, pk__in=transfer_resource_subset).count()
@@ -183,13 +213,22 @@ def transfer_resources(self, request, pk=None):
183213
we can use the resource manager because inside it will automatically update
184214
the owner
185215
"""
186-
perms = permissions_registry.get_perms(instance=instance, include_virtual=False)
187-
prev_owner = get_user_model().objects.filter(pk=previous_owner).first()
188-
189-
if prev_owner and not prev_owner.is_superuser:
190-
perms["users"].pop(prev_owner)
191-
192-
resource_manager.set_permissions(instance.uuid, instance, owner=target or user, permissions=perms)
216+
try:
217+
# putting the resource in dirty state
218+
instance.set_dirty_state()
219+
# updating the perms with the new owner
220+
perms = permissions_registry.get_perms(instance=instance, include_virtual=False)
221+
prev_owner = get_user_model().objects.filter(pk=previous_owner).first()
222+
223+
if prev_owner and not prev_owner.is_superuser:
224+
perms["users"].pop(prev_owner)
225+
# calling the registry to update the perms
226+
resource_manager.set_permissions(instance.uuid, instance, owner=target or user, permissions=perms)
227+
except Exception as e:
228+
logger.exeption(e)
229+
finally:
230+
# clearing the dirty state
231+
instance.clear_dirty_state()
193232

194233
return Response("Resources transfered successfully", status=200)
195234

geonode/people/tests.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from geonode.layers.models import Dataset
3636
from geonode.people import profileextractors
3737

38-
from geonode.base.populate_test_data import all_public, create_models, remove_models
38+
from geonode.base.populate_test_data import all_public, create_models, create_single_dataset, remove_models
3939
from django.db.models import Q
4040
from geonode.security.registry import permissions_registry
4141

@@ -1150,7 +1150,8 @@ def test_transfer_resources_all(self):
11501150
self.assertTrue(bobby_resources.exists())
11511151
# call api
11521152
response = self.client.post(
1153-
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources", data={"owner": norman.id}
1153+
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources",
1154+
data={"currentOwner": bobby.id, "newOwner": norman.id},
11541155
)
11551156
# check that bobby owns the resources no more
11561157
self.assertFalse(bobby_resources.exists())
@@ -1174,7 +1175,8 @@ def test_transfer_resources_invalid_user(self):
11741175
self.assertTrue(bobby_resources.exists())
11751176
# call api
11761177
response = self.client.post(
1177-
path=f"{reverse('users-list')}/{bobby}/transfer_resources", data={"owner": invalid_user_id}
1178+
path=f"{reverse('users-list')}/{bobby}/transfer_resources",
1179+
data={"currentOwner": bobby.id, "newOwner": invalid_user_id},
11781180
)
11791181
# response should be 404
11801182
self.assertEqual(response.status_code, 404)
@@ -1200,7 +1202,8 @@ def test_transfer_resources_default(self):
12001202
self.assertTrue(bobby_resources.exists())
12011203
# call api
12021204
response = self.client.post(
1203-
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources", data={"owner": "DEFAULT"}
1205+
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources",
1206+
data={"currentOwner": bobby.id, "newOwner": "DEFAULT"},
12041207
)
12051208
self.assertTrue(response.status_code == 200)
12061209
# check that bobby owns the resources no more
@@ -1232,7 +1235,8 @@ def test_transfer_resources_to_missing_default(self):
12321235
self.assertTrue(bobby_resources.exists())
12331236
# call api
12341237
response = self.client.post(
1235-
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources", data={"owner": "DEFAULT"}
1238+
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources",
1239+
data={"newOwner": "DEFAULT", "previousOwner": bobby.pk},
12361240
)
12371241
self.assertTrue(response.status_code == 500)
12381242
self.assertEqual(response.data, "Principal User not found")
@@ -1257,7 +1261,8 @@ def test_transfer_resources_to_self(self):
12571261

12581262
# call api
12591263
response = self.client.post(
1260-
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources", data={"owner": bobby.pk}
1264+
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources",
1265+
data={"previousOwner": bobby.pk, "newOwner": bobby.pk},
12611266
)
12621267
self.assertTrue(response.status_code == 400)
12631268
self.assertEqual(response.data, "Cannot reassign to self")
@@ -1277,7 +1282,6 @@ def test_transfer_resources_nopayload(self):
12771282
bobby_resources = ResourceBase.objects.filter(owner=bobby)
12781283
prior_bobby_resources = bobby_resources.all()
12791284
self.assertTrue(bobby_resources.exists())
1280-
12811285
# call api
12821286
response = self.client.post(path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources", data={})
12831287
# response should be 404
@@ -1296,13 +1300,17 @@ def test_transfer_resource_subset(self):
12961300
self.assertTrue(bobby.is_authenticated)
12971301
# check bobbys resources
12981302
resource_to_transfer = ResourceBase.objects.filter(owner=bobby).first()
1299-
second_resource = ResourceBase.objects.filter(owner=bobby).last()
1303+
second_resource = create_single_dataset("test for api", owner=bobby)
13001304
new_owner = get_user_model().objects.exclude(username__in=["bobby", "AnonymousUser"]).first()
13011305

13021306
# call api to transfer bobby resource to the other user
13031307
response = self.client.post(
13041308
path=f"{reverse('users-list')}/{bobby.pk}/transfer_resources",
1305-
data={"owner": new_owner.pk, "resources": [resource_to_transfer.pk, second_resource.pk]},
1309+
data={
1310+
"newOwner": new_owner.pk,
1311+
"currentOwner": bobby.pk,
1312+
"resources": [resource_to_transfer.pk, second_resource.pk],
1313+
},
13061314
)
13071315
# response should be 200
13081316
self.assertEqual(response.status_code, 200)

0 commit comments

Comments
 (0)