Newer
Older
from django.core import paginator
from django.urls import reverse
from rest_framework import exceptions, mixins, response, viewsets
from rest_framework.decorators import detail_route, list_route
from funkwhale_api.common import preferences
from funkwhale_api.music import models as music_models
from . import activity, authentication, models, renderers, serializers, utils, webfinger
class FederationMixin(object):
def dispatch(self, request, *args, **kwargs):
return HttpResponse(status=405)
return super().dispatch(request, *args, **kwargs)
class SharedViewSet(FederationMixin, viewsets.GenericViewSet):
permission_classes = []
authentication_classes = [authentication.SignatureAuthentication]
renderer_classes = [renderers.ActivityPubRenderer]
@list_route(methods=["post"])
def inbox(self, request, *args, **kwargs):
if request.method.lower() == "post" and request.actor is None:
raise exceptions.AuthenticationFailed(
"You need a valid signature to send an activity"
)
if request.method.lower() == "post":
activity.receive(activity=request.data, on_behalf_of=request.actor)
return response.Response({}, status=200)
class ActorViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
authentication_classes = [authentication.SignatureAuthentication]
permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer]
queryset = models.Actor.objects.local().select_related("user")
serializer_class = serializers.ActorSerializer
@detail_route(methods=["get", "post"])
def inbox(self, request, *args, **kwargs):
if request.method.lower() == "post" and request.actor is None:
raise exceptions.AuthenticationFailed(
"You need a valid signature to send an activity"
)
if request.method.lower() == "post":
activity.receive(activity=request.data, on_behalf_of=request.actor)
return response.Response({}, status=200)
@detail_route(methods=["get", "post"])
def outbox(self, request, *args, **kwargs):
return response.Response({}, status=200)
@detail_route(methods=["get"])
def followers(self, request, *args, **kwargs):
self.get_object()
# XXX to implement
return response.Response({})
@detail_route(methods=["get"])
def following(self, request, *args, **kwargs):
self.get_object()
# XXX to implement
return response.Response({})
class WellKnownViewSet(viewsets.GenericViewSet):
authentication_classes = []
permission_classes = []
Eliot Berriot
committed
renderer_classes = [renderers.JSONRenderer, renderers.WebfingerRenderer]
def nodeinfo(self, request, *args, **kwargs):
return HttpResponse(status=404)
data = {
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
"href": utils.full_url(reverse("api:v1:instance:nodeinfo-2.0")),
}
]
}
return response.Response(data)
def webfinger(self, request, *args, **kwargs):
return HttpResponse(status=405)
resource_type, resource = webfinger.clean_resource(request.GET["resource"])
cleaner = getattr(webfinger, "clean_{}".format(resource_type))
result = cleaner(resource)
handler = getattr(self, "handler_{}".format(resource_type))
data = handler(result)
except forms.ValidationError as e:
return response.Response({"errors": {"resource": e.message}}, status=400)
return response.Response(
{"errors": {"resource": "This field is required"}}, status=400
)
return response.Response(data)
def handler_acct(self, clean_result):
username, hostname = clean_result
try:
actor = models.Actor.objects.local().get(preferred_username=username)
except models.Actor.DoesNotExist:
raise forms.ValidationError("Invalid username")
return serializers.ActorWebfingerSerializer(actor).data
def has_library_access(request, library):
if library.privacy_level == "everyone":
return True
if request.user.is_authenticated and request.user.is_superuser:
return True
try:
actor = request.actor
except AttributeError:
return False
return library.received_follows.filter(actor=actor, approved=True).exists()
class MusicLibraryViewSet(
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
authentication_classes = [authentication.SignatureAuthentication]
renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Library.objects.all().select_related("actor")
lookup_field = "uuid"
def retrieve(self, request, *args, **kwargs):
lb = self.get_object()
conf = {
"id": lb.get_federation_id(),
"actor": lb.actor,
"name": lb.name,
"summary": lb.description,
"items": lb.uploads.for_federation().order_by("-creation_date"),
serializer = serializers.LibrarySerializer(lb)
data = serializer.data
else:
# if actor is requesting a specific page, we ensure library is public
# or readable by the actor
if not has_library_access(request, lb):
raise exceptions.AuthenticationFailed(
"You do not have access to this library"
)
try:
page_number = int(page)
return response.Response({"page": ["Invalid page number"]}, status=400)
conf["page_size"] = preferences.get("federation__collection_page_size")
p = paginator.Paginator(conf["items"], conf["page_size"])
try:
page = p.page(page_number)
serializer = serializers.CollectionPageSerializer(conf)
data = serializer.data
except paginator.EmptyPage:
return response.Response(status=404)
return response.Response(data)
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
@detail_route(methods=["get"])
def followers(self, request, *args, **kwargs):
self.get_object()
# XXX Implement this
return response.Response({})
class MusicUploadViewSet(
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
authentication_classes = [authentication.SignatureAuthentication]
permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Upload.objects.none()
lookup_field = "uuid"
class MusicArtistViewSet(
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
authentication_classes = [authentication.SignatureAuthentication]
permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Artist.objects.none()
lookup_field = "uuid"
class MusicAlbumViewSet(
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
authentication_classes = [authentication.SignatureAuthentication]
permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Album.objects.none()
lookup_field = "uuid"
class MusicTrackViewSet(
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
authentication_classes = [authentication.SignatureAuthentication]
permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer]
queryset = music_models.Track.objects.none()
lookup_field = "uuid"