From bdd3258bb54a1dc6326bcfa66b934fcdd03da1ac Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Thu, 14 Jun 2018 23:17:49 +0200 Subject: [PATCH] See #303: fixed failing tests and improved validation --- .../federation/authentication.py | 1 + api/funkwhale_api/federation/jsonld.py | 21 ++++++++++- api/funkwhale_api/federation/serializers.py | 37 +++++++++++-------- api/tests/federation/test_authentication.py | 5 ++- api/tests/federation/test_jsonld.py | 31 ++++++++++++++++ 5 files changed, 78 insertions(+), 17 deletions(-) diff --git a/api/funkwhale_api/federation/authentication.py b/api/funkwhale_api/federation/authentication.py index f32c78ff3..d51679fc2 100644 --- a/api/funkwhale_api/federation/authentication.py +++ b/api/funkwhale_api/federation/authentication.py @@ -19,6 +19,7 @@ class SignatureAuthentication(authentication.BaseAuthentication): try: actor = actors.get_actor(key_id.split("#")[0]) except Exception as e: + raise raise exceptions.AuthenticationFailed(str(e)) if not actor.public_key: diff --git a/api/funkwhale_api/federation/jsonld.py b/api/funkwhale_api/federation/jsonld.py index b19e775fd..5101faeb6 100644 --- a/api/funkwhale_api/federation/jsonld.py +++ b/api/funkwhale_api/federation/jsonld.py @@ -83,6 +83,7 @@ class Serializer(serializers.Serializer): id_source = None include_id = True include_type = True + valid_types = [] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -104,9 +105,27 @@ class Serializer(serializers.Serializer): ) def run_validation(self, initial_data): - document = expand(initial_data)[0] + try: + document = expand(initial_data)[0] + except IndexError: + raise serializers.ValidationError( + "Cannot parse json-ld, maybe you are missing context." + ) return super().run_validation(document) + def validate(self, validated_data): + validated_data = super().validate(validated_data) + if self.valid_types and "@type" in validated_data: + if validated_data["@type"] not in self.valid_types: + raise serializers.ValidationError( + { + "@type": "Invalid type. Allowed types are: {}".format( + ", ".join(self.valid_types) + ) + } + ) + return validated_data + def to_representation(self, data): data = super().to_representation(data) compacted = pyld.jsonld.compact(data, {"@context": ns.CONTEXTS}) diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index 7c5454392..d9229f685 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -52,6 +52,13 @@ class PublicKeyField(jsonld.Serializer): class ActorSerializer(jsonld.Serializer): id_source = "url" type_source = "type_ap" + valid_types = [ + ns.SHORT["as:Application"], + ns.SHORT["as:Group"], + ns.SHORT["as:Organization"], + ns.SHORT["as:Person"], + ns.SHORT["as:Service"], + ] jsonld_fields = [ ( ns.SHORT["as:outbox"], @@ -119,8 +126,8 @@ class ActorSerializer(jsonld.Serializer): "url": self.validated_data["url"], "outbox_url": self.validated_data[ns.SHORT["as:outbox"]], "inbox_url": self.validated_data[ns.SHORT["ldp:inbox"]], - "following_url": self.validated_data[ns.SHORT["as:following"]], - "followers_url": self.validated_data[ns.SHORT["as:followers"]], + "following_url": self.validated_data.get(ns.SHORT["as:following"]), + "followers_url": self.validated_data.get(ns.SHORT["as:followers"]), "summary": self.validated_data.get(ns.SHORT["as:summary"]), "type": self.validated_data["type_ap"].split("#")[-1], "name": self.validated_data.get(ns.SHORT["as:name"]), @@ -178,22 +185,22 @@ class APIActorSerializer(serializers.ModelSerializer): class LibraryActorSerializer(ActorSerializer): - url = serializers.ListField(child=serializers.JSONField()) + # url = serializers.ListField(child=serializers.JSONField()) + jsonld_fields = ActorSerializer.jsonld_fields + [ + ( + ns.SHORT["as:url"], + jsonld.Serializer(required=True), + {"source": "library_url"}, + ) + ] def validate(self, validated_data): - try: - urls = validated_data["url"] - except KeyError: - raise serializers.ValidationError("Missing URL field") - - for u in urls: - try: - if u["name"] != "library": - continue - validated_data["library_url"] = u["href"] - break - except KeyError: + validated_data = super().validate(validated_data) + for url in validated_data[ns.SHORT["as:url"]]: + if url[ns.SHORT["as:name"]][0]["@value"] != "library": continue + validated_data["library_url"] = url[ns.SHORT["as:href"]][0]["@id"] + break return validated_data diff --git a/api/tests/federation/test_authentication.py b/api/tests/federation/test_authentication.py index 95cec5d2a..e9371f010 100644 --- a/api/tests/federation/test_authentication.py +++ b/api/tests/federation/test_authentication.py @@ -1,4 +1,4 @@ -from funkwhale_api.federation import authentication, keys +from funkwhale_api.federation import authentication, keys, ns def test_authenticate(factories, mocker, api_request): @@ -7,10 +7,13 @@ def test_authenticate(factories, mocker, api_request): mocker.patch( "funkwhale_api.federation.actors.get_actor_data", return_value={ + "@context": ns.CONTEXTS, "id": actor_url, "type": "Person", "outbox": "https://test.com", "inbox": "https://test.com", + "following": "https://test.com", + "followers": "https://test.com", "preferredUsername": "test", "publicKey": { "publicKeyPem": public.decode("utf-8"), diff --git a/api/tests/federation/test_jsonld.py b/api/tests/federation/test_jsonld.py index a8681006e..7d6712ac5 100644 --- a/api/tests/federation/test_jsonld.py +++ b/api/tests/federation/test_jsonld.py @@ -93,6 +93,37 @@ def test_json_ld_serializer_validation_id(field_conf, expected): } +def test_json_ld_validates_type_failing(): + document = { + "@context": ns.NS["as"]["documentUrl"], + "type": "Disallowed", + "outbox": "http://test", + "id": "http://test.document", + } + + class Serializer(jsonld.Serializer): + valid_types = [ns.SHORT["as:Person"]] + + serializer = Serializer(data=document) + assert serializer.is_valid() is False + assert "@type" in serializer.errors + + +def test_json_ld_validates_type_success(): + document = { + "@context": ns.NS["as"]["documentUrl"], + "type": "Person", + "outbox": "http://test", + "id": "http://test.document", + } + + class Serializer(jsonld.Serializer): + valid_types = [ns.SHORT["as:Person"]] + + serializer = Serializer(data=document) + assert serializer.is_valid(raise_exception=True) is True + + def test_json_ld_serializer_to_representation(): class Serializer(jsonld.Serializer): jsonld_fields = [ -- GitLab