From 6dcde77b1e33e10bfb6c30d80a39820fce32e352 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Sat, 28 Apr 2018 00:25:47 +0200 Subject: [PATCH] Avoid fetching Actor object on every request authentication --- api/config/settings/common.py | 4 +++ api/funkwhale_api/federation/actors.py | 12 ++++++- .../federation/authentication.py | 17 +++------ api/funkwhale_api/federation/serializers.py | 7 ++-- api/tests/federation/test_actors.py | 36 +++++++++++++++++++ changes/changelog.d/actor-fetch.enhancement | 1 + .../src/components/federation/LibraryCard.vue | 2 ++ front/src/views/federation/LibraryDetail.vue | 1 + 8 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 changes/changelog.d/actor-fetch.enhancement diff --git a/api/config/settings/common.py b/api/config/settings/common.py index f1a383c58..2b9580b04 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -56,6 +56,10 @@ FEDERATION_COLLECTION_PAGE_SIZE = env.int( FEDERATION_MUSIC_NEEDS_APPROVAL = env.bool( 'FEDERATION_MUSIC_NEEDS_APPROVAL', default=True ) +# how much minutes to wait before refetching actor data +# when authenticating +FEDERATION_ACTOR_FETCH_DELAY = env.int( + 'FEDERATION_ACTOR_FETCH_DELAY', default=60 * 12) ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS') # APP CONFIGURATION diff --git a/api/funkwhale_api/federation/actors.py b/api/funkwhale_api/federation/actors.py index 380bb23c0..a0e23d12f 100644 --- a/api/funkwhale_api/federation/actors.py +++ b/api/funkwhale_api/federation/actors.py @@ -1,3 +1,4 @@ +import datetime import logging import uuid import xml @@ -49,11 +50,20 @@ def get_actor_data(actor_url): def get_actor(actor_url): + try: + actor = models.Actor.objects.get(url=actor_url) + except models.Actor.DoesNotExist: + actor = None + fetch_delta = datetime.timedelta( + minutes=settings.FEDERATION_ACTOR_FETCH_DELAY) + if actor and actor.last_fetch_date > timezone.now() - fetch_delta: + # cache is hot, we can return as is + return actor data = get_actor_data(actor_url) serializer = serializers.ActorSerializer(data=data) serializer.is_valid(raise_exception=True) - return serializer.build() + return serializer.save(last_fetch_date=timezone.now()) class SystemActor(object): diff --git a/api/funkwhale_api/federation/authentication.py b/api/funkwhale_api/federation/authentication.py index 7f8ad6653..bfd46084c 100644 --- a/api/funkwhale_api/federation/authentication.py +++ b/api/funkwhale_api/federation/authentication.py @@ -25,28 +25,19 @@ class SignatureAuthentication(authentication.BaseAuthentication): raise exceptions.AuthenticationFailed(str(e)) try: - actor_data = actors.get_actor_data(key_id) + actor = actors.get_actor(key_id.split('#')[0]) except Exception as e: raise exceptions.AuthenticationFailed(str(e)) - try: - public_key = actor_data['publicKey']['publicKeyPem'] - except KeyError: + if not actor.public_key: raise exceptions.AuthenticationFailed('No public key found') - serializer = serializers.ActorSerializer(data=actor_data) - if not serializer.is_valid(): - raise exceptions.AuthenticationFailed('Invalid actor payload: {}'.format(serializer.errors)) - try: - signing.verify_django(request, public_key.encode('utf-8')) + signing.verify_django(request, actor.public_key.encode('utf-8')) except cryptography.exceptions.InvalidSignature: raise exceptions.AuthenticationFailed('Invalid signature') - try: - return models.Actor.objects.get(url=actor_data['id']) - except models.Actor.DoesNotExist: - return serializer.save() + return actor def authenticate(self, request): setattr(request, 'actor', None) diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index 38efdd3bf..426aabd77 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -103,9 +103,10 @@ class ActorSerializer(serializers.Serializer): def save(self, **kwargs): d = self.prepare_missing_fields() d.update(kwargs) - return models.Actor.objects.create( - **d - ) + return models.Actor.objects.update_or_create( + url=d['url'], + defaults=d, + )[0] def validate_summary(self, value): if value: diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py index 7281147a1..f566e7a72 100644 --- a/api/tests/federation/test_actors.py +++ b/api/tests/federation/test_actors.py @@ -29,6 +29,42 @@ def test_actor_fetching(r_mock): assert r == payload +def test_get_actor(factories, r_mock): + actor = factories['federation.Actor'].build() + payload = serializers.ActorSerializer(actor).data + r_mock.get(actor.url, json=payload) + new_actor = actors.get_actor(actor.url) + + assert new_actor.pk is not None + assert serializers.ActorSerializer(new_actor).data == payload + + +def test_get_actor_use_existing(factories, settings, mocker): + settings.FEDERATION_ACTOR_FETCH_DELAY = 60 + actor = factories['federation.Actor']() + get_data = mocker.patch('funkwhale_api.federation.actors.get_actor_data') + new_actor = actors.get_actor(actor.url) + + assert new_actor == actor + get_data.assert_not_called() + + +def test_get_actor_refresh(factories, settings, mocker): + settings.FEDERATION_ACTOR_FETCH_DELAY = 0 + actor = factories['federation.Actor']() + payload = serializers.ActorSerializer(actor).data + # actor changed their username in the meantime + payload['preferredUsername'] = 'New me' + get_data = mocker.patch( + 'funkwhale_api.federation.actors.get_actor_data', + return_value=payload) + new_actor = actors.get_actor(actor.url) + + assert new_actor == actor + assert new_actor.last_fetch_date > actor.last_fetch_date + assert new_actor.preferred_username == 'New me' + + def test_get_library(db, settings, mocker): get_key_pair = mocker.patch( 'funkwhale_api.federation.keys.get_key_pair', diff --git a/changes/changelog.d/actor-fetch.enhancement b/changes/changelog.d/actor-fetch.enhancement new file mode 100644 index 000000000..17f3a88df --- /dev/null +++ b/changes/changelog.d/actor-fetch.enhancement @@ -0,0 +1 @@ +Avoid fetching Actor object on every request authentication diff --git a/front/src/components/federation/LibraryCard.vue b/front/src/components/federation/LibraryCard.vue index a16c80f7e..e7ef7a516 100644 --- a/front/src/components/federation/LibraryCard.vue +++ b/front/src/components/federation/LibraryCard.vue @@ -3,10 +3,12 @@
{{ displayName }} + {{ displayName }}
diff --git a/front/src/views/federation/LibraryDetail.vue b/front/src/views/federation/LibraryDetail.vue index 64f2cc7c6..7a842b679 100644 --- a/front/src/views/federation/LibraryDetail.vue +++ b/front/src/views/federation/LibraryDetail.vue @@ -150,6 +150,7 @@ export default { methods: { fetchData () { var self = this + this.scanTrigerred = false this.isLoading = true let url = 'federation/libraries/' + this.id + '/' logger.default.debug('Fetching library "' + this.id + '"')