Remove special remote user handling code

also fixes date parsing
This commit is contained in:
Mouse Reeve 2020-11-28 11:48:17 -08:00
parent fd7e476c9b
commit b0202eb8e8
7 changed files with 102 additions and 151 deletions

View file

@ -118,11 +118,12 @@ class ActivityObject:
formatted_value = mapping.model_formatter(value) formatted_value = mapping.model_formatter(value)
if isinstance(model_field, DeferredAttribute) and \ if isinstance(model_field, DeferredAttribute) and \
isinstance(model_field.field, DateTimeField): isinstance(model_field.field, DateTimeField):
print("DATE")
try: try:
formatted_value = timezone.make_aware( date_value = dateutil.parser.parse(formatted_value)
dateutil.parser.parse(formatted_value) try:
) formatted_value = timezone.make_aware(date_value)
except ValueError:
formatted_value = date_value
except ParserError: except ParserError:
formatted_value = None formatted_value = None
elif isinstance(model_field, ForwardManyToOneDescriptor) and \ elif isinstance(model_field, ForwardManyToOneDescriptor) and \

View file

@ -10,7 +10,6 @@ import requests
from bookwyrm import activitypub, books_manager, models, outgoing from bookwyrm import activitypub, books_manager, models, outgoing
from bookwyrm import status as status_builder from bookwyrm import status as status_builder
from bookwyrm.remote_user import get_or_create_remote_user, refresh_remote_user
from bookwyrm.tasks import app from bookwyrm.tasks import app
from bookwyrm.signatures import Signature from bookwyrm.signatures import Signature
@ -96,13 +95,15 @@ def has_valid_signature(request, activity):
if key_actor != activity.get('actor'): if key_actor != activity.get('actor'):
raise ValueError("Wrong actor created signature.") raise ValueError("Wrong actor created signature.")
remote_user = get_or_create_remote_user(key_actor) remote_user = activitypub.resolve_remote_id(models.User, key_actor)
try: try:
signature.verify(remote_user.public_key, request) signature.verify(remote_user.public_key, request)
except ValueError: except ValueError:
old_key = remote_user.public_key old_key = remote_user.public_key
refresh_remote_user(remote_user) activitypub.resolve_remote_id(
models.User, remote_user, refresh=True
)
if remote_user.public_key == old_key: if remote_user.public_key == old_key:
raise # Key unchanged. raise # Key unchanged.
signature.verify(remote_user.public_key, request) signature.verify(remote_user.public_key, request)
@ -127,7 +128,7 @@ def handle_follow(activity):
return return
# figure out who the actor is # figure out who the actor is
actor = get_or_create_remote_user(activity['actor']) actor = activitypub.resolve_remote_id(models.User, activity['actor'])
try: try:
relationship = models.UserFollowRequest.objects.create( relationship = models.UserFollowRequest.objects.create(
user_subject=actor, user_subject=actor,
@ -162,7 +163,7 @@ def handle_follow(activity):
def handle_unfollow(activity): def handle_unfollow(activity):
''' unfollow a local user ''' ''' unfollow a local user '''
obj = activity['object'] obj = activity['object']
requester = get_or_create_remote_user(obj['actor']) requester = activitypub.resolve_remote_id(models.user, obj['actor'])
to_unfollow = models.User.objects.get(remote_id=obj['object']) to_unfollow = models.User.objects.get(remote_id=obj['object'])
# raises models.User.DoesNotExist # raises models.User.DoesNotExist
@ -175,7 +176,7 @@ def handle_follow_accept(activity):
# figure out who they want to follow # figure out who they want to follow
requester = models.User.objects.get(remote_id=activity['object']['actor']) requester = models.User.objects.get(remote_id=activity['object']['actor'])
# figure out who they are # figure out who they are
accepter = get_or_create_remote_user(activity['actor']) accepter = activitypub.resolve_remote_id(models.User, activity['actor'])
try: try:
request = models.UserFollowRequest.objects.get( request = models.UserFollowRequest.objects.get(
@ -192,7 +193,7 @@ def handle_follow_accept(activity):
def handle_follow_reject(activity): def handle_follow_reject(activity):
''' someone is rejecting a follow request ''' ''' someone is rejecting a follow request '''
requester = models.User.objects.get(remote_id=activity['object']['actor']) requester = models.User.objects.get(remote_id=activity['object']['actor'])
rejecter = get_or_create_remote_user(activity['actor']) rejecter = activitypub.resolve_remote_id(models.User, activity['actor'])
request = models.UserFollowRequest.objects.get( request = models.UserFollowRequest.objects.get(
user_subject=requester, user_subject=requester,
@ -205,25 +206,27 @@ def handle_follow_reject(activity):
@app.task @app.task
def handle_create(activity): def handle_create(activity):
''' someone did something, good on them ''' ''' someone did something, good on them '''
if activity['object'].get('type') not in \
['Note', 'Comment', 'Quotation', 'Review', 'GeneratedNote']:
# if it's an article or unknown type, ignore it
return
user = get_or_create_remote_user(activity['actor'])
if user.local:
# we really oughtn't even be sending in this case
return
# deduplicate incoming activities # deduplicate incoming activities
status_id = activity['object']['id'] status_id = activity['object']['id']
if models.Status.objects.filter(remote_id=status_id).count(): if models.Status.objects.filter(remote_id=status_id).count():
return return
status = status_builder.create_status(activity['object']) serializer = activitypub.activity_objects[activity['type']]
if not status: status = serializer(**activity)
try:
model = models.activity_models[activity.type]
except KeyError:
# not a type of status we are prepared to deserialize
return return
if activity.type == 'Note':
reply = models.Status.objects.filter(
remote_id=activity.inReplyTo
).first()
if not reply:
return
activity.to_model(model)
# create a notification if this is a reply # create a notification if this is a reply
if status.reply_parent and status.reply_parent.user.local: if status.reply_parent and status.reply_parent.user.local:
status_builder.create_notification( status_builder.create_notification(
@ -257,16 +260,14 @@ def handle_favorite(activity):
''' approval of your good good post ''' ''' approval of your good good post '''
fav = activitypub.Like(**activity) fav = activitypub.Like(**activity)
liker = get_or_create_remote_user(activity['actor'])
if liker.local:
return
fav = fav.to_model(models.Favorite) fav = fav.to_model(models.Favorite)
if fav.user.local:
return
status_builder.create_notification( status_builder.create_notification(
fav.status.user, fav.status.user,
'FAVORITE', 'FAVORITE',
related_user=liker, related_user=fav.user,
related_status=fav.status, related_status=fav.status,
) )

View file

@ -1,5 +1,6 @@
''' database schema for user data ''' ''' database schema for user data '''
from urllib.parse import urlparse from urllib.parse import urlparse
import requests
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
@ -7,10 +8,12 @@ from django.dispatch import receiver
from bookwyrm import activitypub from bookwyrm import activitypub
from bookwyrm.models.shelf import Shelf from bookwyrm.models.shelf import Shelf
from bookwyrm.models.status import Status from bookwyrm.models.status import Status, Review
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
from bookwyrm.signatures import create_key_pair from bookwyrm.signatures import create_key_pair
from bookwyrm.tasks import app
from .base_model import ActivityMapping, OrderedCollectionPageMixin from .base_model import ActivityMapping, OrderedCollectionPageMixin
from .federated_server import FederatedServer
class User(OrderedCollectionPageMixin, AbstractUser): class User(OrderedCollectionPageMixin, AbstractUser):
@ -188,7 +191,16 @@ class User(OrderedCollectionPageMixin, AbstractUser):
@receiver(models.signals.post_save, sender=User) @receiver(models.signals.post_save, sender=User)
def execute_after_save(sender, instance, created, *args, **kwargs): def execute_after_save(sender, instance, created, *args, **kwargs):
''' create shelves for new users ''' ''' create shelves for new users '''
if not instance.local or not created: if not created:
return
if not instance.local:
actor_parts = urlparse(instance.remote_id)
instance.federated_server = \
get_or_create_remote_server(actor_parts.netloc)
instance.save()
if instance.bookwyrm_user:
get_remote_reviews.delay(instance.outbox)
return return
shelves = [{ shelves = [{
@ -209,3 +221,56 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
user=instance, user=instance,
editable=False editable=False
).save() ).save()
def get_or_create_remote_server(domain):
''' get info on a remote server '''
try:
return FederatedServer.objects.get(
server_name=domain
)
except FederatedServer.DoesNotExist:
pass
response = requests.get(
'https://%s/.well-known/nodeinfo' % domain,
headers={'Accept': 'application/activity+json'}
)
if response.status_code != 200:
return None
data = response.json()
try:
nodeinfo_url = data.get('links')[0].get('href')
except (TypeError, KeyError):
return None
response = requests.get(
nodeinfo_url,
headers={'Accept': 'application/activity+json'}
)
data = response.json()
server = FederatedServer.objects.create(
server_name=domain,
application_type=data['software']['name'],
application_version=data['software']['version'],
)
return server
@app.task
def get_remote_reviews(outbox):
''' ingest reviews by a new remote bookwyrm user '''
outbox_page = outbox + '?page=true'
response = requests.get(
outbox_page,
headers={'Accept': 'application/activity+json'}
)
data = response.json()
# TODO: pagination?
for activity in data['orderedItems']:
if not activity['type'] == 'Review':
continue
activitypub.Review(**activity).to_model(Review)

View file

@ -12,7 +12,6 @@ from bookwyrm.broadcast import broadcast
from bookwyrm.status import create_notification from bookwyrm.status import create_notification
from bookwyrm.status import create_generated_note from bookwyrm.status import create_generated_note
from bookwyrm.status import delete_status from bookwyrm.status import delete_status
from bookwyrm.remote_user import get_or_create_remote_user
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
from bookwyrm.utils import regex from bookwyrm.utils import regex
@ -61,9 +60,11 @@ def handle_remote_webfinger(query):
return None return None
data = response.json() data = response.json()
for link in data['links']: for link in data['links']:
if link['rel'] == 'self': if link.get('rel') == 'self':
try: try:
user = get_or_create_remote_user(link['href']) user = activitypub.resolve_remote_id(
models.User, link['href']
)
except KeyError: except KeyError:
return None return None
return user return user

View file

@ -1,86 +0,0 @@
''' manage remote users '''
from urllib.parse import urlparse
import requests
from django.db import transaction
from bookwyrm import activitypub, models
from bookwyrm import status as status_builder
from bookwyrm.tasks import app
def get_or_create_remote_user(actor):
''' look up a remote user or add them '''
try:
return models.User.objects.get(remote_id=actor)
except models.User.DoesNotExist:
pass
actor_parts = urlparse(actor)
with transaction.atomic():
user = activitypub.resolve_remote_id(models.User, actor)
user.federated_server = get_or_create_remote_server(actor_parts.netloc)
user.save()
if user.bookwyrm_user:
get_remote_reviews.delay(user.id)
return user
def refresh_remote_user(user):
''' get updated user data from its home instance '''
activitypub.resolve_remote_id(user.remote_id, refresh=True)
@app.task
def get_remote_reviews(user_id):
''' ingest reviews by a new remote bookwyrm user '''
try:
user = models.User.objects.get(id=user_id)
except models.User.DoesNotExist:
return
outbox_page = user.outbox + '?page=true'
response = requests.get(
outbox_page,
headers={'Accept': 'application/activity+json'}
)
data = response.json()
# TODO: pagination?
for activity in data['orderedItems']:
status_builder.create_status(activity)
def get_or_create_remote_server(domain):
''' get info on a remote server '''
try:
return models.FederatedServer.objects.get(
server_name=domain
)
except models.FederatedServer.DoesNotExist:
pass
response = requests.get(
'https://%s/.well-known/nodeinfo' % domain,
headers={'Accept': 'application/activity+json'}
)
if response.status_code != 200:
return None
data = response.json()
try:
nodeinfo_url = data.get('links')[0].get('href')
except (TypeError, KeyError):
return None
response = requests.get(
nodeinfo_url,
headers={'Accept': 'application/activity+json'}
)
data = response.json()
server = models.FederatedServer.objects.create(
server_name=domain,
application_type=data['software']['name'],
application_version=data['software']['version'],
)
return server

View file

@ -12,37 +12,6 @@ def delete_status(status):
status.save() status.save()
def create_status(activity):
''' unfortunately, it's not QUITE as simple as deserializing it '''
# render the json into an activity object
serializer = activitypub.activity_objects[activity['type']]
activity = serializer(**activity)
try:
model = models.activity_models[activity.type]
except KeyError:
# not a type of status we are prepared to deserialize
return None
# ignore notes that aren't replies to known statuses
if activity.type == 'Note':
reply = models.Status.objects.filter(
remote_id=activity.inReplyTo
).first()
if not reply:
return None
# look up books
book_urls = []
if hasattr(activity, 'inReplyToBook'):
book_urls.append(activity.inReplyToBook)
if hasattr(activity, 'tag'):
book_urls += [t['href'] for t in activity.tag if t['type'] == 'Book']
for remote_id in book_urls:
books_manager.get_or_create_book(remote_id)
return activity.to_model(model)
def create_generated_note(user, content, mention_books=None, privacy='public'): def create_generated_note(user, content, mention_books=None, privacy='public'):
''' a note created by the app about user activity ''' ''' a note created by the app about user activity '''
# sanitize input html # sanitize input html

View file

@ -24,4 +24,4 @@ app.autodiscover_tasks(['bookwyrm'], related_name='books_manager')
app.autodiscover_tasks(['bookwyrm'], related_name='emailing') app.autodiscover_tasks(['bookwyrm'], related_name='emailing')
app.autodiscover_tasks(['bookwyrm'], related_name='goodreads_import') app.autodiscover_tasks(['bookwyrm'], related_name='goodreads_import')
app.autodiscover_tasks(['bookwyrm'], related_name='incoming') app.autodiscover_tasks(['bookwyrm'], related_name='incoming')
app.autodiscover_tasks(['bookwyrm'], related_name='remote_user') app.autodiscover_tasks(['bookwyrm'], related_name='models.user')