Пример #1
0
def test_dereference():

    followers_doc = {
        "@context": jsonld.get_default_context(),
        "id": "https://noop/federation/actors/demo/followers",
        "type": "Collection",
    }

    actor_doc = {
        "@context": jsonld.get_default_context(),
        "id": "https://noop/federation/actors/demo",
        "type": "Person",
        "followers": "https://noop/federation/actors/demo/followers",
    }

    store = {followers_doc["id"]: followers_doc, actor_doc["id"]: actor_doc}

    payload = {
        "followers": {"@id": followers_doc["id"]},
        "actor": [
            {"@id": actor_doc["id"], "hello": "world"},
            {"somethingElse": [{"@id": actor_doc["id"]}]},
        ],
    }
    expected = {
        "followers": followers_doc,
        "actor": [actor_doc, {"somethingElse": [actor_doc]}],
    }

    assert jsonld.dereference(payload, store) == expected
Пример #2
0
def test_jsonld_serializer_dereference(a_responses):
    class TestSerializer(jsonld.JsonLdSerializer):
        id = serializers.URLField()
        type = serializers.CharField()
        followers = serializers.JSONField()

        class Meta:
            jsonld_mapping = {
                "followers": {"property": contexts.AS.followers, "dereference": True}
            }

    payload = {
        "@context": jsonld.get_default_context(),
        "id": "https://noop.url/federation/actors/demo",
        "type": "Person",
        "followers": "https://noop.url/federation/actors/demo/followers",
    }

    followers_doc = {
        "@context": jsonld.get_default_context(),
        "id": "https://noop.url/federation/actors/demo/followers",
        "type": "Collection",
    }

    a_responses.get(followers_doc["id"], payload=followers_doc)
    serializer = TestSerializer(data=payload)

    assert serializer.is_valid(raise_exception=True)
    assert serializer.validated_data == {
        "type": contexts.AS.Person,
        "id": payload["id"],
        "followers": [followers_doc],
    }
Пример #3
0
def test_inbox_create_audio(factories, mocker):
    activity = factories["federation.Activity"]()
    upload = factories["music.Upload"](bitrate=42,
                                       duration=55,
                                       track__album__with_cover=True)
    payload = {
        "@context": jsonld.get_default_context(),
        "type": "Create",
        "actor": upload.library.actor.fid,
        "object": serializers.UploadSerializer(upload).data,
    }
    library = upload.library
    upload.delete()
    init = mocker.spy(serializers.UploadSerializer, "__init__")
    save = mocker.spy(serializers.UploadSerializer, "save")
    assert library.uploads.count() == 0
    result = routes.inbox_create_audio(
        payload,
        context={
            "actor": library.actor,
            "raise_exception": True,
            "activity": activity
        },
    )
    assert library.uploads.count() == 1
    assert result == {
        "object": library.uploads.latest("id"),
        "target": library
    }

    assert init.call_count == 1
    args = init.call_args
    assert args[1]["data"] == payload["object"]
    assert args[1]["context"] == {"activity": activity, "actor": library.actor}
    assert save.call_count == 1
Пример #4
0
async def test_fetch_many(a_responses):
    doc = {
        "@context": jsonld.get_default_context(),
        "id": "https://noop/federation/actors/demo",
        "type": "Person",
        "followers": "https://noop/federation/actors/demo/followers",
    }
    followers_doc = {
        "@context": jsonld.get_default_context(),
        "id": "https://noop/federation/actors/demo/followers",
        "type": "Collection",
    }

    a_responses.get(doc["id"], payload=doc)
    a_responses.get(followers_doc["id"], payload=followers_doc)
    fetched = await jsonld.fetch_many(doc["id"], followers_doc["id"])
    assert fetched == {followers_doc["id"]: followers_doc, doc["id"]: doc}
Пример #5
0
def test_fetch_skipped(factories, r_mock):
    url = "https://fetch.object"
    fetch = factories["federation.Fetch"](url=url)
    payload = {"@context": jsonld.get_default_context(), "type": "Unhandled"}
    r_mock.get(url, json=payload)

    tasks.fetch(fetch_id=fetch.pk)

    fetch.refresh_from_db()

    assert fetch.status == "skipped"
    assert fetch.detail["reason"] == "unhandled_type"
Пример #6
0
def test_jsonld_serializer_fallback():
    class TestSerializer(jsonld.JsonLdSerializer):
        id = serializers.URLField()
        type = serializers.CharField()
        name = serializers.CharField()
        username = serializers.CharField()
        total = serializers.IntegerField()

        class Meta:
            jsonld_fallbacks = {"total": ["total_fallback"]}
            jsonld_mapping = {
                "name": {
                    "property": contexts.AS.name,
                    "keep": "first",
                    "attr": "@value",
                },
                "username": {
                    "property": contexts.AS.preferredUsername,
                    "keep": "first",
                    "attr": "@value",
                },
                "total": {
                    "property": contexts.AS.totalItems,
                    "keep": "first",
                    "attr": "@value",
                },
                "total_fallback": {
                    "property": contexts.NOOP.count,
                    "keep": "first",
                    "attr": "@value",
                },
            }

    payload = {
        "@context": jsonld.get_default_context(),
        "id": "https://noop.url/federation/actors/demo",
        "type": "Person",
        "name": "Hello",
        "preferredUsername": "******",
        "count": 42,
    }

    serializer = TestSerializer(data=payload)
    assert serializer.is_valid(raise_exception=True)

    assert serializer.validated_data == {
        "type": contexts.AS.Person,
        "id": payload["id"],
        "name": payload["name"],
        "username": payload["preferredUsername"],
        "total": 42,
    }
Пример #7
0
def test_authenticate(factories, mocker, api_request):
    private, public = keys.get_key_pair()
    factories["federation.Domain"](name="test.federation",
                                   nodeinfo_fetch_date=None)
    actor_url = "https://test.federation/actor"
    mocker.patch(
        "funkwhale_api.federation.actors.get_actor_data",
        return_value={
            "@context": jsonld.get_default_context(),
            "id": actor_url,
            "type": "Person",
            "outbox": "https://test.com",
            "inbox": "https://test.com",
            "followers": "https://test.com",
            "preferredUsername": "******",
            "publicKey": {
                "publicKeyPem": public.decode("utf-8"),
                "owner": actor_url,
                "id": actor_url + "#main-key",
            },
        },
    )
    update_domain_nodeinfo = mocker.patch(
        "funkwhale_api.federation.tasks.update_domain_nodeinfo")

    signed_request = factories["federation.SignedRequest"](
        auth__key=private,
        auth__key_id=actor_url + "#main-key",
        auth__headers=["date"])
    prepared = signed_request.prepare()
    django_request = api_request.get(
        "/", **{
            "HTTP_DATE": prepared.headers["date"],
            "HTTP_SIGNATURE": prepared.headers["signature"],
        })
    authenticator = authentication.SignatureAuthentication()
    user, _ = authenticator.authenticate(django_request)
    actor = django_request.actor

    assert user.is_anonymous is True
    assert actor.public_key == public.decode("utf-8")
    assert actor.fid == actor_url
    update_domain_nodeinfo.assert_called_once_with(
        domain_name="test.federation")
Пример #8
0
def test_autenthicate_supports_blind_key_rotation(factories, mocker,
                                                  api_request):
    actor = factories["federation.Actor"]()
    actor_url = actor.fid
    # request is signed with a pair of new keys
    new_private, new_public = keys.get_key_pair()
    mocker.patch(
        "funkwhale_api.federation.actors.get_actor_data",
        return_value={
            "@context": jsonld.get_default_context(),
            "id": actor_url,
            "type": "Person",
            "outbox": "https://test.com",
            "inbox": "https://test.com",
            "followers": "https://test.com",
            "preferredUsername": "******",
            "publicKey": {
                "publicKeyPem": new_public.decode("utf-8"),
                "owner": actor_url,
                "id": actor_url + "#main-key",
            },
        },
    )
    signed_request = factories["federation.SignedRequest"](
        auth__key=new_private,
        auth__key_id=actor_url + "#main-key",
        auth__headers=["date"],
    )
    prepared = signed_request.prepare()
    django_request = api_request.get(
        "/", **{
            "HTTP_DATE": prepared.headers["date"],
            "HTTP_SIGNATURE": prepared.headers["signature"],
        })
    authenticator = authentication.SignatureAuthentication()
    user, _ = authenticator.authenticate(django_request)
    actor = django_request.actor

    assert user.is_anonymous is True
    assert actor.public_key == new_public.decode("utf-8")
    assert actor.fid == actor_url
Пример #9
0
def test_authenticate_ignore_inactive_policy(factories, api_request, mocker):
    policy = factories["moderation.InstancePolicy"](block_all=True,
                                                    for_domain=True,
                                                    is_active=False)
    private, public = keys.get_key_pair()
    actor_url = "https://{}/actor".format(policy.target_domain.name)

    signed_request = factories["federation.SignedRequest"](
        auth__key=private,
        auth__key_id=actor_url + "#main-key",
        auth__headers=["date"])
    mocker.patch(
        "funkwhale_api.federation.actors.get_actor_data",
        return_value={
            "@context": jsonld.get_default_context(),
            "id": actor_url,
            "type": "Person",
            "outbox": "https://test.com",
            "inbox": "https://test.com",
            "followers": "https://test.com",
            "preferredUsername": "******",
            "publicKey": {
                "publicKeyPem": public.decode("utf-8"),
                "owner": actor_url,
                "id": actor_url + "#main-key",
            },
        },
    )
    prepared = signed_request.prepare()
    django_request = api_request.get(
        "/", **{
            "HTTP_DATE": prepared.headers["date"],
            "HTTP_SIGNATURE": prepared.headers["signature"],
        })
    authenticator = authentication.SignatureAuthentication()
    authenticator.authenticate(django_request)
    actor = django_request.actor

    assert actor.public_key == public.decode("utf-8")
    assert actor.fid == actor_url
Пример #10
0
def test_inbox_create_audio_channel(factories, mocker):
    activity = factories["federation.Activity"]()
    channel = factories["audio.Channel"]()
    album = factories["music.Album"](artist=channel.artist)
    upload = factories["music.Upload"](
        track__album=album,
        library=channel.library,
    )
    payload = {
        "@context": jsonld.get_default_context(),
        "type": "Create",
        "actor": channel.actor.fid,
        "object": serializers.ChannelUploadSerializer(upload).data,
    }
    upload.delete()
    init = mocker.spy(serializers.ChannelCreateUploadSerializer, "__init__")
    save = mocker.spy(serializers.ChannelCreateUploadSerializer, "save")
    result = routes.inbox_create_audio(
        payload,
        context={
            "actor": channel.actor,
            "raise_exception": True,
            "activity": activity
        },
    )
    assert channel.library.uploads.count() == 1
    assert result == {
        "object": channel.library.uploads.latest("id"),
        "target": channel
    }

    assert init.call_count == 1
    args = init.call_args
    assert args[1]["data"] == payload
    assert args[1]["context"] == {"channel": channel}
    assert save.call_count == 1
Пример #11
0
def test_fetch_collection(mocker, r_mock):
    class DummySerializer(serializers.serializers.Serializer):
        def validate(self, validated_data):
            validated_data = self.initial_data
            if "id" not in validated_data["object"]:
                raise serializers.serializers.ValidationError()
            return validated_data

        def save(self):
            return self.initial_data

    mocker.patch.object(
        tasks,
        "COLLECTION_ACTIVITY_SERIALIZERS",
        [({
            "type": "Create",
            "object.type": "Audio"
        }, DummySerializer)],
    )
    payloads = {
        "outbox": {
            "id": "https://actor.url/outbox",
            "@context": jsonld.get_default_context(),
            "type": "OrderedCollection",
            "totalItems": 27094,
            "first": "https://actor.url/outbox?page=1",
            "last": "https://actor.url/outbox?page=3",
        },
        "page1": {
            "@context":
            jsonld.get_default_context(),
            "type":
            "OrderedCollectionPage",
            "next":
            "https://actor.url/outbox?page=2",
            "orderedItems": [
                {
                    "type": "Unhandled"
                },
                {
                    "type": "Unhandled"
                },
                {
                    "type": "Create",
                    "object": {
                        "type": "Audio",
                        "id": "https://actor.url/audio1"
                    },
                },
            ],
        },
        "page2": {
            "@context":
            jsonld.get_default_context(),
            "type":
            "OrderedCollectionPage",
            "next":
            "https://actor.url/outbox?page=3",
            "orderedItems": [
                {
                    "type": "Unhandled"
                },
                {
                    "type": "Create",
                    "object": {
                        "type": "Audio",
                        "id": "https://actor.url/audio2"
                    },
                },
                {
                    "type": "Unhandled"
                },
                {
                    "type": "Create",
                    "object": {
                        "type": "Audio"
                    }
                },
            ],
        },
    }
    r_mock.get(payloads["outbox"]["id"], json=payloads["outbox"])
    r_mock.get(payloads["outbox"]["first"], json=payloads["page1"])
    r_mock.get(payloads["page1"]["next"], json=payloads["page2"])
    result = tasks.fetch_collection(
        payloads["outbox"]["id"],
        max_pages=2,
    )
    assert result["items"] == [
        payloads["page1"]["orderedItems"][2],
        payloads["page2"]["orderedItems"][1],
    ]
    assert result["skipped"] == 4
    assert result["errored"] == 1
    assert result["seen"] == 7
    assert result["total"] == 27094
    assert result["next_page"] == payloads["page2"]["next"]
Пример #12
0
    assert fetch.status == "skipped"
    assert fetch.detail["reason"] == "unhandled_type"


@pytest.mark.parametrize(
    "r_mock_args, expected_error_code",
    [
        ({
            "json": {
                "type": "Unhandled"
            }
        }, "invalid_jsonld"),
        ({
            "json": {
                "@context": jsonld.get_default_context()
            }
        }, "invalid_jsonld"),
        ({
            "text": "invalidjson"
        }, "invalid_json"),
        ({
            "status_code": 404
        }, "http"),
        ({
            "status_code": 500
        }, "http"),
    ],
)
def test_fetch_errored(factories, r_mock_args, expected_error_code, r_mock):
    url = "https://fetch.object"