forked from mirrors/bookwyrm
Remove special remote user handling code
also fixes date parsing
This commit is contained in:
parent
fd7e476c9b
commit
b0202eb8e8
7 changed files with 102 additions and 151 deletions
|
@ -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 \
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in a new issue