def test_atstyle(self): httpretty.register_uri( 'GET', EXAMPLE_USER_URL, status=200, headers={ 'Content-Type': 'application/activity+json', }, body=EXAMPLE_USER_RESULT, ) httpretty.register_uri( 'GET', EXAMPLE_WEBFINGER_URL, status=200, headers={ 'Content-Type': 'application/jrd+json', }, body=EXAMPLE_WEBFINGER_RESULT, ) user = fetch(EXAMPLE_ATSTYLE, RemotePerson) self._asserts_for_example_user(user)
def test_fetch_known_user(self): existing = RemotePerson(remote_url=EXAMPLE_USER_URL, ) existing.save() found = {} def finding(request, uri, headers): found['found'] = True return EXAMPLE_USER_RESULT httpretty.register_uri( 'GET', EXAMPLE_USER_URL, status=200, headers={ 'Content-Type': 'application/activity+json', }, body=finding, ) user = fetch(EXAMPLE_USER_URL, RemotePerson) self.assertNotIn( 'found', found, msg="Known remote user wasn't re-fetched", )
def test_url_404(self): found = fetch('https://testserver/users/bob', expected_type=Person) self.assertEqual( found, None, )
def __next__(self): if self._iter_items: return self._iter_items.pop(0) if self._next_page is None: logger.debug("%s: iteration: finished!", self.url) raise StopIteration logger.debug("%s: iteration: fetching %s...", self.url, self._next_page) import kepi.sombrero_sendpub.fetch as fetch next_bit = fetch.fetch( self._next_page, expected_type=_CollectionPage, ) if next_bit is None: logger.info("%s: error in fetching items", self.url) raise StopIteration self._iter_items = next_bit.items self._next_page = next_bit.next logger.debug(' -- containing %s', self._iter_items) return self._iter_items.pop(0)
def test_when_sender_is_followed_by_local_users(self): from kepi.trilby_api.models import Follow, Person local_user = create_local_person() remote_alice = fetch(REMOTE_ALICE, expected_type=Person) following = Follow( follower=local_user, following=remote_alice, ) following.save() object_form = { 'id': 'https://example.com/some-note', 'type': 'Note', 'content': 'Lorem ipsum', } status = self._send_create_for_object(object_form, sender=remote_alice) self.assertIsNotNone( status, msg='it creates status', ) self.assertEqual( status.text, 'Lorem ipsum', msg='it creates status text', )
def test_fetch_simple_collection(self): httpretty.register_uri( 'GET', EXAMPLE_SIMPLE_COLLECTION_URL, status=200, headers={ 'Content-Type': 'application/activity+json', }, body=EXAMPLE_SIMPLE_COLLECTION, ) collection = fetch(EXAMPLE_SIMPLE_COLLECTION_URL, expected_type=Collection) self.assertEqual(sorted(collection), EXAMPLE_SIMPLE_COLLECTION_MEMBERS, msg="Collection can be iterated") self.assertEqual(sorted(collection), EXAMPLE_SIMPLE_COLLECTION_MEMBERS, msg="Collection can be iterated twice") self.assertEqual(len(collection), len(EXAMPLE_SIMPLE_COLLECTION_MEMBERS), msg="Collection has a length")
def test_url(self): found = fetch('https://testserver/users/alice', expected_type=Person) self.assertEqual( found, self._alice, )
def test_atstyle_404(self): found = fetch('bob@testserver', expected_type=Person) self.assertEqual( found, None, )
def test_url_wrong_type(self): found = fetch('https://testserver/users/bob/outbox', expected_type=Person) self.assertEqual( found, None, )
def test_atstyle(self): found = fetch('alice@testserver', expected_type=Person) self.assertEqual( found, self._alice, )
def on_announce(fields, address): logger.debug('%s: on_announce %s', address, fields) try: if isinstance(fields.get('object', None), dict): # We don't trust an object passed to us as part of # an Announce, because it generally comes from a # different user. So we take the id and go and # look it up for ourselves. status_url = fields['object']['id'] else: status_url = fields['object'] except FieldError as fe: logger.info("%s: unusable object field: %s", address, fe) return None status = sombrero_fetch.fetch(status_url, expected_type = trilby_models.Status, ) if status is None: logger.info("%s: attempted to reblog non-existent status %s", address, status_url) return None actor = sombrero_fetch.fetch(fields['actor'], expected_type = trilby_models.Person, ) logger.debug('%s: reblogging status %s by %s', address, status_url, actor) reblog = trilby_models.Status( account = actor, reblog_of = status, ) reblog.save() logger.debug('%s: created reblog: %s', address, reblog) return reblog
def on_follow(fields, address): logger.debug('%s: on_follow %s', address, fields) if not bowler_utils.is_local(fields['object']): logger.info("%s: ignoring someone following non-local user", address) return None follower = sombrero_fetch.fetch( fields['actor'], expected_type = trilby_models.Person, ) if follower is None: # shouldn't happen logger.warning('%s: could not find remote user %s', address, fields['actor'], ) return None following = sombrero_fetch.fetch( fields['object'], expected_type = trilby_models.Person, ) if following is None: logger.info('%s: there is no local user %s', address, fields['object'], ) return None result = trilby_models.Follow( follower = follower, following = following, offer = fields.get('id'), ) result.save( send_signal = True, ) return result
def test_atstyle_410(self): httpretty.register_uri( 'GET', EXAMPLE_WEBFINGER_URL, status=410, headers={ 'Content-Type': 'text/plain', }, body="not any more!", ) fetch(EXAMPLE_ATSTYLE, RemotePerson) users = RemotePerson.objects.filter(acct=EXAMPLE_ATSTYLE) self.assertEqual( len(users), 0, )
def test_atstyle_no_activity(self): httpretty.register_uri( 'GET', EXAMPLE_WEBFINGER_URL, status=200, headers={ 'Content-Type': 'application/jrd+json', }, body=EXAMPLE_WEBFINGER_RESULT_NO_USER, ) fetch(EXAMPLE_ATSTYLE, RemotePerson) users = RemotePerson.objects.filter(acct=EXAMPLE_ATSTYLE) self.assertEqual( len(users), 0, )
def on_like(fields, address): logger.debug('%s: on_like %s', address, fields) liker = sombrero_fetch.fetch( fields['actor'], expected_type = trilby_models.Person, ) if liker is None: # shouldn't happen logger.warning('%s: could not find user %s', address, fields['actor'], ) return None liked = sombrero_fetch.fetch( fields['object'], expected_type = trilby_models.Status, ) if liked is None: logger.info('%s: could not find status %s', address, fields['object'], ) return None like = trilby_models.Like( liker = liker, liked = liked, ) like.save( send_signal = True, ) return like
def test_fetch_410(self): httpretty.register_uri( 'GET', EXAMPLE_USER_URL, status=410, headers={ 'Content-Type': 'text/plain', }, body='not any more!', ) user = fetch(EXAMPLE_USER_URL, RemotePerson) self.assertIsNone(user, )
def test_fetch_no_such_host(self): def no_such_host(request, uri, headers): raise requests.ConnectionError() httpretty.register_uri( 'GET', EXAMPLE_USER_URL, status=200, headers={ 'Content-Type': 'text/plain', }, body=no_such_host, ) with suppress_thread_exceptions(): user = fetch(EXAMPLE_USER_URL, RemotePerson) self.assertIsNone(user, )
def __next__(self): logger.debug("%s RemotePerson: finding next...", self.address, ) url = self.collection.__next__() logger.debug("%s RemotePerson: next is at %s", self.address, url, ) person = fetch( url, Person, ) logger.debug("%s RemotePerson: -- which is %s", url, person, ) return person
def __iter__(self): remote_collection = fetch( self.address, Collection, ) if remote_collection is None: logger.debug( "%s RemotePerson: could not retrieve collection", self.address, ) self.collection = [].__iter__() return self self.collection = remote_collection.__iter__() logger.debug( "%s: retrieved collection %s", self.address, self.collection, ) return self
def _run_validation_inner(message, ): """ Validates a message. Don't call this function directly; call validate(), above. Returns True iff the message is valid. message_id -- the primary key of an IncomingMessage that was generated by validate(). """ logger.info('%s: begin validation', message) try: key_id = message.key_id except ValueError: logger.warning('%s: message is unsigned; dropping', message) return False try: from kepi.trilby_api.models import Person actor = fetch(message.actor, Person) except json.decoder.JSONDecodeError as jde: logger.info('%s: invalid JSON; dropping: %s', message, jde) return False except UnicodeDecodeError: logger.info('%s: invalid UTF-8; dropping', message) return False if actor is None: logger.info('%s: remote actor does not exist; dropping message', message) # FIXME: If this message is an instruction to delete a remote user, # it's valid if the remote user is Gone. Need to pass this out # from fetch() somehow. return False logger.debug('%s: message signature is: %s', message, message.signature) logger.debug('%s: message body is: %s', message, message.body) logger.debug('%s: actor details are: %s', message, actor) # XXX key used to sign must "_obviously_belong_to" the actor try: key = actor.publicKey except TypeError as te: logger.info( '%s: actor has an invalid public key (%s); dropping message', message, te, ) return False logger.debug('Verifying; key=%s, path=%s, host=%s', key, message.path, message.host) logger.debug( 'All params: %s', { 'headers': { 'Content-Type': message.content_type, 'Date': message.date, 'Signature': message.signature, 'Host': message.host, 'Digest': message.digest, }, 'secret': key, 'method': 'POST', 'path': message.path, 'host': message.host, 'sign_header': 'Signature', }) hv = HeaderVerifier( headers={ 'Content-Type': message.content_type, 'Date': message.date, 'Signature': message.signature, 'Host': message.host, 'Digest': message.digest, }, secret=key, method='POST', path=message.path, host=message.host, sign_header='Signature', ) if not hv.verify(): logger.info('%s: spoofing attempt; message dropped', message) return False logger.debug('%s: validation passed!', message) return True
def on_note(fields, address): logger.debug("Looking up actor: %s", fields['attributedTo']) poster = sombrero_fetch.fetch( fields['attributedTo'], expected_type = trilby_models.Person, ) if poster is None: logger.debug(" -- who does not exist") return None logger.debug(" -- who is %s", poster) if 'inReplyTo' in fields: in_reply_to = sombrero_fetch.fetch( fields['inReplyTo'], expected_type = trilby_models.Status, ) else: in_reply_to = None is_sensitive = False # FIXME spoiler_text = '' # FIXME language = 'en' # FIXME visibility = _visibility_from_fields( fields) logger.debug('%s: creating status from %s', address, fields, ) try: newbie = trilby_models.Status( remote_url = fields['id'], account = poster, in_reply_to = in_reply_to, content = fields['content'], sensitive = is_sensitive, spoiler_text = spoiler_text, visibility = visibility, language = language, ) newbie.save() logger.debug('%s: created status %s', address, newbie, ) except KeyError as ke: logger.debug('%s: missing field: %s', address, ke) return None except Exception as e: logger.debug('%s: failed to create status: %s', address, e) return None if 'tag' in fields: logger.debug('%s: adding tags', address) for tag in fields['tag']: if 'type' not in tag or 'href' not in tag: logger.debug('%s: -- missing fields: %s', address, tag) continue if tag['type'].lower() != 'mention': logger.debug('%s: -- unknown tag type: %s', address, tag) continue logger.debug('%s: -- %s', address, tag['href']) whom = sombrero_fetch.fetch(tag['href'], expected_type = trilby_models.Person) if whom is None: logger.debug('%s: -- not found', address) continue mention = trilby_models.Mention( status = newbie, whom = whom, ) mention.save() logger.debug('%s: -- %s', address, mention) logger.debug('%s: -- tags done', address) return newbie