Merge pull request #625 from mouse-reeve/inbox-refactor

Inbox refactor
This commit is contained in:
Mouse Reeve
2021-02-24 13:34:59 -08:00
committed by GitHub
43 changed files with 958 additions and 887 deletions

View File

@ -11,6 +11,7 @@ from .follow import follow, unfollow
from .follow import accept_follow_request, delete_follow_request
from .goal import Goal
from .import_data import Import, ImportStatus
from .inbox import Inbox
from .interaction import Favorite, Unfavorite, Boost, Unboost
from .invite import ManageInvites, Invite
from .landing import About, Home, Discover

View File

@ -1,5 +1,6 @@
''' views for actions you can take in the application '''
from django.contrib.auth.decorators import login_required
from django.db import IntegrityError
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect
from django.views.decorators.http import require_POST
@ -17,13 +18,14 @@ def follow(request):
except models.User.DoesNotExist:
return HttpResponseBadRequest()
rel, _ = models.UserFollowRequest.objects.get_or_create(
user_subject=request.user,
user_object=to_follow,
)
try:
models.UserFollowRequest.objects.create(
user_subject=request.user,
user_object=to_follow,
)
except IntegrityError:
pass
if to_follow.local and not to_follow.manually_approves_followers:
rel.accept()
return redirect(to_follow.local_path)
@ -40,9 +42,7 @@ def unfollow(request):
models.UserFollows.objects.get(
user_subject=request.user,
user_object=to_unfollow
)
to_unfollow.followers.remove(request.user)
).delete()
return redirect(to_unfollow.local_path)

View File

@ -192,7 +192,7 @@ def handle_remote_webfinger(query):
if link.get('rel') == 'self':
try:
user = activitypub.resolve_remote_id(
models.User, link['href']
link['href'], model=models.User
)
except KeyError:
return None

95
bookwyrm/views/inbox.py Normal file
View File

@ -0,0 +1,95 @@
''' incoming activities '''
import json
from urllib.parse import urldefrag
from django.http import HttpResponse
from django.http import HttpResponseBadRequest, HttpResponseNotFound
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
import requests
from bookwyrm import activitypub, models
from bookwyrm.tasks import app
from bookwyrm.signatures import Signature
@method_decorator(csrf_exempt, name='dispatch')
# pylint: disable=no-self-use
class Inbox(View):
''' requests sent by outside servers'''
def post(self, request, username=None):
''' only works as POST request '''
# first let's do some basic checks to see if this is legible
if username:
try:
models.User.objects.get(localname=username)
except models.User.DoesNotExist:
return HttpResponseNotFound()
# is it valid json? does it at least vaguely resemble an activity?
try:
activity_json = json.loads(request.body)
except json.decoder.JSONDecodeError:
return HttpResponseBadRequest()
# verify the signature
if not has_valid_signature(request, activity_json):
if activity_json['type'] == 'Delete':
# Pretend that unauth'd deletes succeed. Auth may be failing
# because the resource or owner of the resource might have
# been deleted.
return HttpResponse()
return HttpResponse(status=401)
# just some quick smell tests before we try to parse the json
if not 'object' in activity_json or \
not 'type' in activity_json or \
not activity_json['type'] in activitypub.activity_objects:
return HttpResponseNotFound()
activity_task.delay(activity_json)
return HttpResponse()
@app.task
def activity_task(activity_json):
''' do something with this json we think is legit '''
# lets see if the activitypub module can make sense of this json
try:
activity = activitypub.parse(activity_json)
except activitypub.ActivitySerializerError:
return
# cool that worked, now we should do the action described by the type
# (create, update, delete, etc)
activity.action()
def has_valid_signature(request, activity):
''' verify incoming signature '''
try:
signature = Signature.parse(request)
key_actor = urldefrag(signature.key_id).url
if key_actor != activity.get('actor'):
raise ValueError("Wrong actor created signature.")
remote_user = activitypub.resolve_remote_id(
key_actor, model=models.User)
if not remote_user:
return False
try:
signature.verify(remote_user.key_pair.public_key, request)
except ValueError:
old_key = remote_user.key_pair.public_key
remote_user = activitypub.resolve_remote_id(
remote_user.remote_id, model=models.User, refresh=True
)
if remote_user.key_pair.public_key == old_key:
raise # Key unchanged.
signature.verify(remote_user.key_pair.public_key, request)
except (ValueError, requests.exceptions.HTTPError):
return False
return True

View File

@ -1,6 +1,5 @@
''' tagging views'''
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseNotFound
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
@ -16,12 +15,11 @@ class Tag(View):
''' tag page '''
def get(self, request, tag_id):
''' see books related to a tag '''
tag_obj = models.Tag.objects.filter(identifier=tag_id).first()
if not tag_obj:
return HttpResponseNotFound()
tag_obj = get_object_or_404(models.Tag, identifier=tag_id)
if is_api_request(request):
return ActivitypubResponse(tag_obj.to_activity(**request.GET))
return ActivitypubResponse(
tag_obj.to_activity(**request.GET))
books = models.Edition.objects.filter(
usertag__tag__identifier=tag_id