Skip to content

Commit

Permalink
[Fixes #ISSUE_11995] GNIP 99: Implement a CRUD REST API for users (#1…
Browse files Browse the repository at this point in the history
…2072)

* [Fixes #11995 Implement POST and PATCH methods for the User API (#12011)

* [Fixes #11995] Implement POST and PATCH methods for the User API
* Upgrare Remote Docker for CircleCI
* [Fixes #11995] Implement POST and PATCH methods for the User API, refactored validation in serializer

---------

Co-authored-by: RegisSinjari <[email protected]>
Co-authored-by: Giovanni Allegri <[email protected]>

* [Fixes #11995] Implement the DELETE method for the User API (#12028)

* [Fixes #11995] Implement the DELETE method for the User API

* [Fixes #11995] Implement the DELETE method for the User API refactor and docstrings added

* [Fixes #11995] Implement endpoint to transfer resources (#12067)

* [Fixes #11995] Implement endpoint to transfer ownership

* [Issue 11995] Implement endpoint to unregister as a project manager (#12066)

* [FIXES #11995] Implement endpoint to unregister as a project manager

* [FIXES #11995] Implement endpoint to unregister as a project managergroup.group_id

* [FIXES #11995] Implement endpoint to unregister as a project manager,tests added

* [Fixes #11995] black reformatting

* [Fixes #11995] GNIP 99: Implement a CRUD REST API for users

* [Fixes #11995] GNIP 99: Implement a CRUD REST API for users

* Update views.py

---------

Co-authored-by: RegisSinjari <[email protected]>
Co-authored-by: Giovanni Allegri <[email protected]>
  • Loading branch information
3 people committed Mar 21, 2024
1 parent 5187f70 commit 9e39774
Show file tree
Hide file tree
Showing 7 changed files with 1,014 additions and 13 deletions.
6 changes: 6 additions & 0 deletions geonode/base/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from rest_framework import permissions
from rest_framework.filters import BaseFilterBackend
from geonode.people.utils import get_available_users
from geonode.security.permissions import (
BASIC_MANAGE_PERMISSIONS,
DOWNLOAD_PERMISSIONS,
Expand Down Expand Up @@ -139,6 +140,11 @@ def has_object_permission(self, request, view, obj):
elif hasattr(obj, "user"):
_request_matches = obj.user == request.user

if isinstance(obj, get_user_model()) and not request.user.is_anonymous:
if request.method in permissions.SAFE_METHODS and obj in get_available_users(request.user):
return True
return _request_matches

if not _request_matches:
_request_matches = request.user in get_users_with_perms(obj)
return _request_matches
Expand Down
35 changes: 35 additions & 0 deletions geonode/base/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#
#########################################################################
import logging

from slugify import slugify
from urllib.parse import urljoin
import json
Expand All @@ -30,6 +31,9 @@
from django.db.models.query import QuerySet
from geonode.people import Roles
from django.http import QueryDict
from django.contrib.auth.hashers import make_password
from django.contrib.auth.password_validation import validate_password
from django.forms import ValidationError as ValidationErrorForm

from deprecated import deprecated
from rest_framework import serializers
Expand Down Expand Up @@ -358,6 +362,37 @@ class Meta:
view_name = "users-list"
fields = ("pk", "username", "first_name", "last_name", "avatar", "perms", "is_superuser", "is_staff", "email")

@staticmethod
def password_validation(password_payload):
try:
validate_password(password_payload)
except ValidationErrorForm as err:
raise serializers.ValidationError(detail=",".join(err.messages))
return make_password(password_payload)

def validate(self, data):
request = self.context["request"]
user = request.user
# only admins/staff can edit these permissions
if not (user.is_superuser or user.is_staff):
data.pop("is_superuser", None)
data.pop("is_staff", None)
# username cant be changed
if request.method in ("PUT", "PATCH") and data.get("username"):
raise serializers.ValidationError(detail="username cannot be updated")
email = data.get("email")
# Email is required on post
if request.method in ("POST") and settings.ACCOUNT_EMAIL_REQUIRED and not email:
raise serializers.ValidationError(detail="email missing from payload")
# email should be unique
if get_user_model().objects.filter(email=email).exists():
raise serializers.ValidationError("A user is already registered with that email")
# password validation
password = request.data.get("password")
if password:
data["password"] = self.password_validation(password)
return data

@classmethod
def setup_eager_loading(cls, queryset):
"""Perform necessary eager loading of data."""
Expand Down
23 changes: 14 additions & 9 deletions geonode/base/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,7 @@ def test_register_users(self):
Ensure users are created with default groups.
"""
url = reverse("users-list")
user_data = {
"username": "new_user",
}
user_data = {"username": "new_user", "password": "@!2XJSL_S&V^0nt", "email": "[email protected]"}
self.assertTrue(self.client.login(username="admin", password="admin"))
response = self.client.post(url, data=user_data, format="json")
self.assertEqual(response.status_code, 201)
Expand Down Expand Up @@ -419,7 +417,7 @@ def test_update_user_profile(self):
username="user_test_delete", email="[email protected]", password="user"
)
url = reverse("users-detail", kwargs={"pk": user.pk})
data = {"first_name": "user"}
data = {"first_name": "user", "password": "@!2XJSL_S&V^0nt", "email": "[email protected]"}
# Anonymous
response = self.client.patch(url, data=data, format="json")
self.assertEqual(response.status_code, 403)
Expand All @@ -430,14 +428,15 @@ def test_update_user_profile(self):
# User self profile
self.assertTrue(self.client.login(username="user_test_delete", password="user"))
response = self.client.patch(url, data=data, format="json")
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 200)
# Group manager
group = GroupProfile.objects.create(slug="test_group_manager", title="test_group_manager")
group.join(user)
group.join(get_user_model().objects.get(username="norman"), role="manager")
self.assertTrue(self.client.login(username="norman", password="norman"))
response = self.client.post(url, data=data, format="json")
self.assertEqual(response.status_code, 403)
# malformed url on post
self.assertEqual(response.status_code, 405)
# Admin can edit user
self.assertTrue(self.client.login(username="admin", password="admin"))
response = self.client.patch(url, data={"first_name": "user_admin"}, format="json")
Expand Down Expand Up @@ -466,14 +465,20 @@ def test_delete_user_profile(self):
self.assertTrue(self.client.login(username="bobby", password="bob"))
response = self.client.delete(url, format="json")
self.assertEqual(response.status_code, 403)
# User can not delete self profile
# User can delete self profile
self.assertTrue(self.client.login(username="user_test_delete", password="user"))
response = self.client.delete(url, format="json")
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 200)
self.assertEqual(get_user_model().objects.filter(username="user_test_delete").first(), None)
# recreate user that was deleted
user = get_user_model().objects.create_user(
username="user_test_delete", email="[email protected]", password="user"
)
url = reverse("users-detail", kwargs={"pk": user.pk})
# Admin can delete user
self.assertTrue(self.client.login(username="admin", password="admin"))
response = self.client.delete(url, format="json")
self.assertEqual(response.status_code, 204)
self.assertEqual(response.status_code, 200)
finally:
user.delete()

Expand Down
Loading

0 comments on commit 9e39774

Please sign in to comment.