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
def get_or_create_book(self, remote_id): edition = activitypub.resolve_remote_id(remote_id, model=models.Edition) work = edition.parent_work work.default_edition = work.get_default_edition() work.save() return edition
def handle_remote_webfinger(query): ''' webfingerin' other servers ''' user = None # usernames could be @user@domain or user@domain if not query: return None if query[0] == '@': query = query[1:] try: domain = query.split('@')[1] except IndexError: return None try: user = models.User.objects.get(username=query) except models.User.DoesNotExist: url = 'https://%s/.well-known/webfinger?resource=acct:%s' % \ (domain, query) try: data = get_data(url) except (ConnectorException, HTTPError): return None for link in data.get('links'): if link.get('rel') == 'self': try: user = activitypub.resolve_remote_id( models.User, link['href'] ) except KeyError: return None return user
def set_field_from_activity(self, instance, data, overwrite=True): if not overwrite: return False original = getattr(instance, self.name) to = data.to cc = data.cc # we need to figure out who this is to get their followers link for field in ["attributedTo", "owner", "actor"]: if hasattr(data, field): user_field = field break if not user_field: raise ValidationError("No user field found for privacy", data) user = activitypub.resolve_remote_id(getattr(data, user_field), model="User") if to == [self.public]: setattr(instance, self.name, "public") elif to == [user.followers_url]: setattr(instance, self.name, "followers") elif cc == []: setattr(instance, self.name, "direct") elif self.public in cc: setattr(instance, self.name, "unlisted") else: setattr(instance, self.name, "followers") return original == getattr(instance, self.name)
def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements """keep notes if they are replies to existing statuses""" if activity.type == "Announce": try: boosted = activitypub.resolve_remote_id(activity.object, get_activity=True) except activitypub.ActivitySerializerError: # if we can't load the status, definitely ignore it return True # keep the boost if we would keep the status return cls.ignore_activity(boosted) # keep if it if it's a custom type if activity.type != "Note": return False # keep it if it's a reply to an existing status if cls.objects.filter(remote_id=activity.inReplyTo).exists(): return False # keep notes if they mention local users if activity.tag == MISSING or activity.tag is None: return True tags = [l["href"] for l in activity.tag if l["type"] == "Mention"] user_model = apps.get_model("bookwyrm.User", require_ready=True) for tag in tags: if user_model.objects.filter(remote_id=tag, local=True).exists(): # we found a mention of a known use boost return False return True
def handle_remote_webfinger(query): """webfingerin' other servers""" user = None # usernames could be @user@domain or user@domain if not query: return None if query[0] == "@": query = query[1:] try: domain = query.split("@")[1] except IndexError: return None try: user = models.User.objects.get(username__iexact=query) except models.User.DoesNotExist: url = "https://%s/.well-known/webfinger?resource=acct:%s" % (domain, query) try: data = get_data(url) except (ConnectorException, HTTPError): return None for link in data.get("links"): if link.get("rel") == "self": try: user = activitypub.resolve_remote_id(link["href"], model=models.User) except (KeyError, activitypub.ActivitySerializerError): return None return user
def handle_follow_reject(activity): ''' someone is rejecting a follow request ''' requester = models.User.objects.get(remote_id=activity['object']['actor']) rejecter = activitypub.resolve_remote_id(models.User, activity['actor']) request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=rejecter) request.delete()
def handle_unfollow(activity): ''' unfollow a local user ''' obj = activity['object'] requester = activitypub.resolve_remote_id(models.User, obj['actor']) to_unfollow = models.User.objects.get(remote_id=obj['object']) # raises models.User.DoesNotExist to_unfollow.followers.remove(requester)
def field_from_activity(self, value): items = [] for remote_id in value: try: validate_remote_id(remote_id) except ValidationError: continue items.append( activitypub.resolve_remote_id(self.related_model, remote_id) ) return items
def field_from_activity(self, value): items = [] if value is None or value is MISSING: return [] for remote_id in value: try: validate_remote_id(remote_id) except ValidationError: continue items.append( activitypub.resolve_remote_id(remote_id, model=self.related_model) ) return items
def handle_follow_accept(activity): ''' hurray, someone remote accepted a follow request ''' # figure out who they want to follow requester = models.User.objects.get(remote_id=activity['object']['actor']) # figure out who they are accepter = activitypub.resolve_remote_id(models.User, activity['actor']) try: request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=accepter) request.delete() except models.UserFollowRequest.DoesNotExist: pass accepter.followers.add(requester)
def field_from_activity(self, value): if not isinstance(value, list): return None items = [] for link_json in value: link = activitypub.Link(**link_json) tag_type = link.type if link.type != 'Mention' else 'Person' if tag_type != self.related_model.activity_serializer.type: # tags can contain multiple types continue items.append( activitypub.resolve_remote_id(self.related_model, link.href) ) return items
def field_from_activity(self, value): if value is None or value is MISSING: return None if not isinstance(value, list): # If this is a link, we currently aren't doing anything with it return None items = [] for remote_id in value: try: validate_remote_id(remote_id) except ValidationError: continue items.append( activitypub.resolve_remote_id(remote_id, model=self.related_model) ) return items
def field_from_activity(self, value): if not value: return None related_model = self.related_model if isinstance(value, dict) and value.get('id'): # this is an activitypub object, which we can deserialize activity_serializer = related_model.activity_serializer return activity_serializer(**value).to_model(related_model) try: # make sure the value looks like a remote id validate_remote_id(value) except ValidationError: # we don't know what this is, ignore it return None # gets or creates the model field from the remote id return activitypub.resolve_remote_id(related_model, value)
def field_from_activity(self, value): if not value: return None related_model = self.related_model if hasattr(value, "id") and value.id: if not self.load_remote: # only look in the local database return related_model.find_existing(value.serialize()) # this is an activitypub object, which we can deserialize return value.to_model(model=related_model) try: # make sure the value looks like a remote id validate_remote_id(value) except ValidationError: # we don't know what this is, ignore it return None # gets or creates the model field from the remote id if not self.load_remote: # only look in the local database return related_model.find_existing_by_remote_id(value) return activitypub.resolve_remote_id(value, model=related_model)
def get_or_create_book(self, remote_id): return activitypub.resolve_remote_id(remote_id, model=models.Edition)