Example #1
0
  def test_activity_to_atom_like(self):
    for obj in {'id': 'foo', 'url': 'foo'}, {'id': 'foo'}, {'url': 'foo'}:
      self.assert_multiline_in("""\
<activity:verb>http://activitystrea.ms/schema/1.0/like</activity:verb>
<activity:object>foo</activity:object>
""", atom.activity_to_atom({
        'url': 'like-url',
        'objectType': 'activity',
        'verb': 'like',
        'object': obj,
      }), ignore_blanks=True)
Example #2
0
 def test_activity_to_atom_author_without_properties(self):
   """https://console.cloud.google.com/errors/CMPawqSghuDquwE"""
   self.assertIn('<title>foo</title>', atom.activity_to_atom({
     'object': {
       'title': 'foo',
       'attachments': [{
         'objectType': 'article',
         'author': {
           'objectType': 'person'
         },
       }],
     }}))
Example #3
0
    def test_activity_to_atom_like(self):
        for obj in {'id': 'foo', 'url': 'foo'}, {'id': 'foo'}, {'url': 'foo'}:
            self.assert_multiline_in("""\
<activity:verb>http://activitystrea.ms/schema/1.0/like</activity:verb>
<activity:object>foo</activity:object>
""",
                                     atom.activity_to_atom({
                                         'url': 'like-url',
                                         'objectType': 'activity',
                                         'verb': 'like',
                                         'object': obj,
                                     }),
                                     ignore_blanks=True)
Example #4
0
 def test_activity_to_atom_author_without_properties(self):
     """https://console.cloud.google.com/errors/CMPawqSghuDquwE"""
     self.assertIn(
         '<title>foo</title>',
         atom.activity_to_atom({
             'object': {
                 'title':
                 'foo',
                 'attachments': [{
                     'objectType': 'article',
                     'author': {
                         'objectType': 'person'
                     },
                 }],
             }
         }))
Example #5
0
    def _try_salmon(self, resp):
        """
        Args:
          resp: Response
        """
        # fetch target HTML page, extract Atom rel-alternate link
        target = resp.target()
        if not self.target_resp:
            self.target_resp = common.requests_get(target)

        parsed = util.parse_html(self.target_resp)
        atom_url = parsed.find('link',
                               rel='alternate',
                               type=common.CONTENT_TYPE_ATOM)
        if not atom_url or not atom_url.get('href'):
            self.error('Target post %s has no Atom link' % resp.target(),
                       status=400)

        # fetch Atom target post, extract and inject id into source object
        base_url = ''
        base = parsed.find('base')
        if base and base.get('href'):
            base_url = base['href']
        atom_link = parsed.find('link',
                                rel='alternate',
                                type=common.CONTENT_TYPE_ATOM)
        atom_url = urllib.parse.urljoin(
            resp.target(), urllib.parse.urljoin(base_url, atom_link['href']))

        feed = common.requests_get(atom_url).text
        parsed = feedparser.parse(feed)
        logging.info('Parsed: %s', json_dumps(parsed, indent=2))
        entry = parsed.entries[0]
        target_id = entry.id
        in_reply_to = self.source_obj.get('inReplyTo')
        source_obj_obj = self.source_obj.get('object')
        if in_reply_to:
            for elem in in_reply_to:
                if elem.get('url') == target:
                    elem['id'] = target_id
        elif isinstance(source_obj_obj, dict):
            source_obj_obj['id'] = target_id

        # Mastodon (and maybe others?) require a rel-mentioned link to the
        # original post's author to make it show up as a reply:
        #   app/services/process_interaction_service.rb
        # ...so add them as a tag, which atom renders as a rel-mention link.
        authors = entry.get('authors', None)
        if authors:
            url = entry.authors[0].get('href')
            if url:
                self.source_obj.setdefault('tags', []).append({'url': url})

        # extract and discover salmon endpoint
        logging.info('Discovering Salmon endpoint in %s', atom_url)
        endpoint = django_salmon.discover_salmon_endpoint(feed)

        if not endpoint:
            # try webfinger
            parsed = urllib.parse.urlparse(resp.target())
            # TODO: test missing email
            author = entry.get('author_detail', {})
            email = author.get('email') or '@'.join(
                (author.get('name', ''), parsed.netloc))
            try:
                # TODO: always https?
                profile = common.requests_get(
                    '%s://%s/.well-known/webfinger?resource=acct:%s' %
                    (parsed.scheme, parsed.netloc, email),
                    verify=False)
                endpoint = django_salmon.get_salmon_replies_link(
                    profile.json())
            except requests.HTTPError as e:
                pass

        if not endpoint:
            self.error('No salmon endpoint found!', status=400)
        logging.info('Discovered Salmon endpoint %s', endpoint)

        # construct reply Atom object
        self.source_url = resp.source()
        activity = self.source_obj
        if self.source_obj.get('verb') not in source.VERBS_WITH_OBJECT:
            activity = {'object': self.source_obj}
        entry = atom.activity_to_atom(activity, xml_base=self.source_url)
        logging.info('Converted %s to Atom:\n%s', self.source_url, entry)

        # sign reply and wrap in magic envelope
        domain = urllib.parse.urlparse(self.source_url).netloc
        key = MagicKey.get_or_create(domain)
        logging.info('Using key for %s: %s', domain, key)
        magic_envelope = magicsigs.magic_envelope(entry,
                                                  common.CONTENT_TYPE_ATOM,
                                                  key).decode()

        logging.info('Sending Salmon slap to %s', endpoint)
        common.requests_post(
            endpoint,
            data=common.XML_UTF8 + magic_envelope,
            headers={'Content-Type': common.CONTENT_TYPE_MAGIC_ENVELOPE})
        return True
Example #6
0
    def send_salmon(self, source_obj, target_resp=None):
        self.resp.protocol = 'ostatus'

        # fetch target HTML page, extract Atom rel-alternate link
        if not target_resp:
            target_resp = common.requests_get(self.resp.target())

        parsed = BeautifulSoup(target_resp.content, from_encoding=target_resp.encoding)
        atom_url = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
        if not atom_url or not atom_url.get('href'):
            common.error(self, 'Target post %s has no Atom link' % self.resp.target(),
                         status=400)

        # fetch Atom target post, extract and inject id into source object
        feed = common.requests_get(atom_url['href']).text
        parsed = feedparser.parse(feed)
        logging.info('Parsed: %s', json.dumps(parsed, indent=2,
                                              default=lambda key: '-'))
        entry = parsed.entries[0]
        target_id = entry.id
        in_reply_to = source_obj.get('inReplyTo')
        source_obj_obj = source_obj.get('object')
        if in_reply_to:
            in_reply_to[0]['id'] = target_id
        elif isinstance(source_obj_obj, dict):
            source_obj_obj['id'] = target_id

        # Mastodon (and maybe others?) require a rel-mentioned link to the
        # original post's author to make it show up as a reply:
        #   app/services/process_interaction_service.rb
        # ...so add them as a tag, which atom renders as a rel-mention link.
        authors = entry.get('authors', None)
        if authors:
            url = entry.authors[0].get('href')
            if url:
                source_obj.setdefault('tags', []).append({'url': url})

        # extract and discover salmon endpoint
        logging.info('Discovering Salmon endpoint in %s', atom_url['href'])
        endpoint = django_salmon.discover_salmon_endpoint(feed)

        if not endpoint:
            # try webfinger
            parsed = urlparse.urlparse(self.resp.target())
            # TODO: test missing email
            email = entry.author_detail.get('email') or '@'.join(
                (entry.author_detail.name, parsed.netloc))
            try:
                # TODO: always https?
                resp = common.requests_get(
                    '%s://%s/.well-known/webfinger?resource=acct:%s' %
                    (parsed.scheme, parsed.netloc, email), verify=False)
                endpoint = django_salmon.get_salmon_replies_link(resp.json())
            except requests.HTTPError as e:
                pass

        if not endpoint:
            common.error(self, 'No salmon endpoint found!', status=400)
        logging.info('Discovered Salmon endpoint %s', endpoint)

        # construct reply Atom object
        source_url = self.resp.source()
        activity = (source_obj if source_obj.get('verb') in source.VERBS_WITH_OBJECT
                    else {'object': source_obj})
        entry = atom.activity_to_atom(activity, xml_base=source_url)
        logging.info('Converted %s to Atom:\n%s', source_url, entry)

        # sign reply and wrap in magic envelope
        domain = urlparse.urlparse(source_url).netloc
        key = MagicKey.get_or_create(domain)
        logging.info('Using key for %s: %s', domain, key)
        magic_envelope = magicsigs.magic_envelope(
            entry, common.CONTENT_TYPE_ATOM, key)

        logging.info('Sending Salmon slap to %s', endpoint)
        common.requests_post(
            endpoint, data=common.XML_UTF8 + magic_envelope,
            headers={'Content-Type': common.CONTENT_TYPE_MAGIC_ENVELOPE})
Example #7
0
 def test_activity_to_atom(self):
     self.assert_multiline_equals(
         INSTAGRAM_ENTRY,
         atom.activity_to_atom(copy.deepcopy(test_instagram.ACTIVITY)),
         ignore_blanks=True)
Example #8
0
 def check():
     got = atom.activity_to_atom(copy.deepcopy(activity), {})
     self.assert_multiline_in('<img src="http://pic" />', got)
     self.assert_multiline_in('<link rel="enclosure" href="http://att"',
                              got)
Example #9
0
 def check():
   got = atom.activity_to_atom(copy.deepcopy(activity), {})
   self.assert_multiline_in('<img src="http://pic" />', got)
   self.assert_multiline_in('<link rel="enclosure" href="http://att"', got)
Example #10
0
 def test_activity_to_atom(self):
   self.assert_multiline_equals(
     INSTAGRAM_ENTRY,
     atom.activity_to_atom(copy.deepcopy(test_instagram.ACTIVITY)),
     ignore_blanks=True)