Merge pull request #5121 from FinnStutzenstein/htmlValidation

Added html validation for users and personal notes
This commit is contained in:
Emanuel Schütze 2019-11-13 14:45:26 +01:00 committed by GitHub
commit 55cdc364f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 1 deletions

View File

@ -9,6 +9,7 @@ from ..utils.rest_api import (
RelatedField,
ValidationError,
)
from ..utils.validate import validate_html
from .models import Group, PersonalNote, User
@ -90,6 +91,11 @@ class UserFullSerializer(ModelSerializer):
data["username"] = User.objects.generate_username(
data.get("first_name", ""), data.get("last_name", "")
)
# check the about_me html
if "about_me" in data:
data["about_me"] = validate_html(data["about_me"])
return data
def prepare_password(self, validated_data):

View File

@ -42,6 +42,7 @@ from ..utils.rest_api import (
list_route,
status,
)
from ..utils.validate import validate_json
from ..utils.views import APIView
from .access_permissions import (
GroupAccessPermissions,
@ -688,7 +689,8 @@ class PersonalNoteViewSet(ModelViewSet):
for data in request.data:
if data["collection"] not in personal_note.notes:
personal_note.notes[data["collection"]] = {}
personal_note.notes[data["collection"]][data["id"]] = data["content"]
content = validate_json(data["content"], 2)
personal_note.notes[data["collection"]][data["id"]] = content
personal_note.save()
return Response()

View File

@ -1,5 +1,9 @@
from typing import Any
import bleach
from .rest_api import ValidationError
allowed_tags = [
"a",
@ -63,3 +67,25 @@ def validate_html(html: str) -> str:
return bleach.clean(
html, tags=allowed_tags, attributes=allowed_attributes, styles=allowed_styles
)
def validate_json(json: Any, max_depth: int) -> Any:
"""
Traverses through the JSON structure (dicts and lists) and runs
validate_html on every found string.
Give max-depth to protect against stack-overflows. This should be the
maximum nested depth of the object expected.
"""
if max_depth == 0:
raise ValidationError({"detail": "The JSON is too nested."})
if isinstance(json, dict):
return {key: validate_json(value, max_depth - 1) for key, value in json.items()}
if isinstance(json, list):
return [validate_json(item, max_depth - 1) for item in json]
if isinstance(json, str):
return validate_html(json)
return json

View File

@ -125,6 +125,20 @@ class UserCreate(TestCase):
{"groups_id": ['Invalid pk "%d" - object does not exist.' % group_pk]},
)
def test_clean_html(self):
self.client.login(username="admin", password="admin")
response = self.client.post(
reverse("user-list"),
{
"username": "test_name_Thimoo2ho7ahreighio3",
"about_me": "<p><foo>bar</foo></p>",
},
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
user = User.objects.get(username="test_name_Thimoo2ho7ahreighio3")
self.assertEqual(user.about_me, "<p>&lt;foo&gt;bar&lt;/foo&gt;</p>")
class UserUpdate(TestCase):
"""
@ -992,6 +1006,44 @@ class PersonalNoteTest(TestCase):
"test_note_do2ncoi7ci2fm93LjwlO",
)
def test_clean_html(self):
admin_client = APIClient()
admin_client.login(username="admin", password="admin")
response = admin_client.post(
reverse("personalnote-create-or-update"),
[
{
"collection": "test_collection",
"id": 1,
"content": {"note": "<p><foo>bar</foo></p>", "star": False},
}
],
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
personal_note = PersonalNote.objects.get()
self.assertEqual(
personal_note.notes["test_collection"]["1"],
{"note": "<p>&lt;foo&gt;bar&lt;/foo&gt;</p>", "star": False},
)
def test_clean_html_content_too_nested(self):
admin_client = APIClient()
admin_client.login(username="admin", password="admin")
response = admin_client.post(
reverse("personalnote-create-or-update"),
[
{
"collection": "test_collection",
"id": 1,
"content": [{"some:key": ["<p><foo>bar</foo></p>"]}, 3],
}
],
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertFalse(PersonalNote.objects.exists())
def test_delete_other_user(self):
user = User.objects.create(username="user")
admin_client = APIClient()