diff --git a/api/funkwhale_api/federation/jsonld.py b/api/funkwhale_api/federation/jsonld.py index 5101faeb6bf33087a2b04b1384dbbf22d6026800..8dce903891c41a6c6db7cd01aad9d43443af286b 100644 --- a/api/funkwhale_api/federation/jsonld.py +++ b/api/funkwhale_api/federation/jsonld.py @@ -53,6 +53,8 @@ class JsonLdField(serializers.ListField): self.id_only = kwargs.pop("id_only", False) self.base_field = kwargs.pop("base_field") super().__init__(*args, **kwargs) + self.child = self.base_field + self.child.parent = self if self.single: self.validators = [ v @@ -105,12 +107,17 @@ class Serializer(serializers.Serializer): ) def run_validation(self, initial_data): + initial_data.setdefault("@context", ns.CONTEXTS) + import ipdb + + ipdb.set_trace() 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): diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index c6a49233b3d12fae79b1269e04d40b65d05251dd..27dc4e4960360ef43caf53924d6f1d16d4116e1d 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -367,18 +367,37 @@ class APILibraryTrackSerializer(serializers.ModelSerializer): return "not_imported" -class FollowSerializer(serializers.Serializer): - id = serializers.URLField(max_length=500) - object = serializers.URLField(max_length=500) - actor = serializers.URLField(max_length=500) - type = serializers.ChoiceField(choices=["Follow"]) +class FollowSerializer(jsonld.Serializer): + valid_types = [ns.SHORT["as:Follow"]] + jsonld_fields = [ + ( + ns.SHORT["as:actor"], + ActorSerializer(required=True), + {"source": "actor", "single": True}, + ), + ( + ns.SHORT["as:object"], + ActorSerializer(required=True), + {"source": "object", "single": True}, + ), + ] + + def validate(self, validated_data): + validated_data = super().validate(validated_data) + validated_data[ns.SHORT["as:object"]] = self.validate_object( + validated_data[ns.SHORT["as:object"]] + ) + validated_data[ns.SHORT["as:actor"]] = self.validate_actor( + validated_data[ns.SHORT["as:actor"]] + ) + return validated_data def validate_object(self, v): expected = self.context.get("follow_target") if expected and expected.url != v: raise serializers.ValidationError("Invalid target") try: - return models.Actor.objects.get(url=v) + return models.Actor.objects.get(url=v["@id"]) except models.Actor.DoesNotExist: raise serializers.ValidationError("Target not found") @@ -387,20 +406,20 @@ class FollowSerializer(serializers.Serializer): if expected and expected.url != v: raise serializers.ValidationError("Invalid actor") try: - return models.Actor.objects.get(url=v) + return models.Actor.objects.get(url=v["@id"]) except models.Actor.DoesNotExist: raise serializers.ValidationError("Actor not found") def save(self, **kwargs): return models.Follow.objects.get_or_create( - actor=self.validated_data["actor"], - target=self.validated_data["object"], + actor=self.validated_data[ns.SHORT["as:actor"]], + target=self.validated_data[ns.SHORT["as:object"]], **kwargs, # noqa )[0] def to_representation(self, instance): return { - "@context": AP_CONTEXT, + "@context": ns.CONTEXTS, "actor": instance.actor.url, "id": instance.get_federation_url(), "object": instance.target.url, @@ -425,30 +444,45 @@ class APIFollowSerializer(serializers.ModelSerializer): ] -class AcceptFollowSerializer(serializers.Serializer): - id = serializers.URLField(max_length=500) - actor = serializers.URLField(max_length=500) - object = FollowSerializer() - type = serializers.ChoiceField(choices=["Accept"]) +class AcceptFollowSerializer(jsonld.Serializer): + valid_types = [ns.SHORT["as:Accept"]] + jsonld_fields = [ + ( + ns.SHORT["as:actor"], + ActorSerializer(required=True), + {"source": "actor", "single": True}, + ), + ( + ns.SHORT["as:object"], + FollowSerializer(required=True), + {"source": "object", "single": True}, + ), + ] def validate_actor(self, v): expected = self.context.get("follow_target") if expected and expected.url != v: raise serializers.ValidationError("Invalid actor") try: - return models.Actor.objects.get(url=v) + return models.Actor.objects.get(url=v["@id"]) except models.Actor.DoesNotExist: raise serializers.ValidationError("Actor not found") def validate(self, validated_data): + import ipdb; ipdb.set_trace() + validated_data = super().validate(validated_data) + validated_data[ns.SHORT["as:actor"]] = self.validate_actor( + validated_data[ns.SHORT["as:actor"]] + ) + follow_target = validated_data[ns.SHORT["as:object"]][ns.SHORT["as:object"]] + follow_actor = validated_data[ns.SHORT["as:object"]][ns.SHORT["as:actor"]] # we ensure the accept actor actually match the follow target - if validated_data["actor"] != validated_data["object"]["object"]: + if validated_data[ns.SHORT["as:actor"]].url != follow_target["@id"]: raise serializers.ValidationError("Actor mismatch") try: validated_data["follow"] = ( models.Follow.objects.filter( - target=validated_data["actor"], - actor=validated_data["object"]["actor"], + target=validated_data[ns.SHORT["as:actor"]], actor=follow_actor ) .exclude(approved=True) .get() diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py index 0ea3c25a3001690815f14336d4414d0f9b4f42fb..a78e5bc8232e1de0f2c433108a4e6ddf92bf27dc 100644 --- a/api/tests/federation/test_serializers.py +++ b/api/tests/federation/test_serializers.py @@ -142,11 +142,7 @@ def test_follow_serializer_to_ap(factories): serializer = serializers.FollowSerializer(follow) expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": ns.CONTEXTS, "id": follow.get_federation_url(), "type": "Follow", "actor": follow.actor.url, @@ -161,6 +157,7 @@ def test_follow_serializer_save(factories): target = factories["federation.Actor"]() data = { + "@context": ns.CONTEXTS, "id": "https://test.follow", "type": "Follow", "actor": actor.url, @@ -184,6 +181,7 @@ def test_follow_serializer_save_validates_on_context(factories): impostor = factories["federation.Actor"]() data = { + "@context": ns.CONTEXTS, "id": "https://test.follow", "type": "Follow", "actor": actor.url, @@ -203,11 +201,7 @@ def test_accept_follow_serializer_representation(factories): follow = factories["federation.Follow"](approved=None) expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": ns.CONTEXTS, "id": follow.get_federation_url() + "/accept", "type": "Accept", "actor": follow.target.url, @@ -223,11 +217,7 @@ def test_accept_follow_serializer_save(factories): follow = factories["federation.Follow"](approved=None) data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": ns.CONTEXTS, "id": follow.get_federation_url() + "/accept", "type": "Accept", "actor": follow.target.url, @@ -247,11 +237,7 @@ def test_accept_follow_serializer_validates_on_context(factories): follow = factories["federation.Follow"](approved=None) impostor = factories["federation.Actor"]() data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": ns.CONTEXTS, "id": follow.get_federation_url() + "/accept", "type": "Accept", "actor": impostor.url, @@ -271,11 +257,7 @@ def test_undo_follow_serializer_representation(factories): follow = factories["federation.Follow"](approved=True) expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": ns.CONTEXTS, "id": follow.get_federation_url() + "/undo", "type": "Undo", "actor": follow.actor.url, @@ -291,11 +273,7 @@ def test_undo_follow_serializer_save(factories): follow = factories["federation.Follow"](approved=True) data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": ns.CONTEXTS, "id": follow.get_federation_url() + "/undo", "type": "Undo", "actor": follow.actor.url, @@ -314,11 +292,7 @@ def test_undo_follow_serializer_validates_on_context(factories): follow = factories["federation.Follow"](approved=True) impostor = factories["federation.Actor"]() data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": ns.CONTEXTS, "id": follow.get_federation_url() + "/undo", "type": "Undo", "actor": impostor.url,