diff --git a/api/funkwhale_api/federation/authentication.py b/api/funkwhale_api/federation/authentication.py index f32c78ff30f00079d822bbf2921d2c4845fff5fc..d51679fc2cbe16fd408a223d9e8551ee9af744ba 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 b19e775fd0872501021f785898e5505321535fde..5101faeb6bf33087a2b04b1384dbbf22d6026800 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 7c5454392d00de34047ea15e8e0aabd92b885403..d9229f6852fad80082b75b8a4e86b732475715b8 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 95cec5d2ac80d94a7b04de1dd5121c3d270b883b..e9371f010c0d5270460e49bbeeff670bb3895492 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 a8681006e3ab79b1d059999c03540fc4577c0140..7d6712ac58afdee34424a6f14e66ccab9eab4164 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 = [