Esempio n. 1
0
    def notify_subscribe(self, contact):
        """
        Notify contact <contact> that they have a new follower (<self>),
        usually by placing an item in their feed. This is so the contact
        can see them and decide if they wish to follow them in return.
        The notice is also placed on <self>'s feed so they can know that
        they sent it.
        """
        from pyaspora.post.models import Post

        assert(self.user or contact.user)
        p = Post(author=self)
        db.session.add(p)

        p.add_part(
            order=0,
            inline=True,
            mime_part=MimePart(
                body=dumps({
                    'from': self.id,
                    'to': contact.id,
                    'to_name': contact.realname
                }).encode('utf-8'),
                type='application/x-pyaspora-subscribe',
                text_preview=u'subscribed to {0}'.format(contact.realname)
            )
        )
        if self.user:
            p.share_with([self])
        if contact.user:
            p.share_with([contact])
        p.thread_modified()
Esempio n. 2
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        node = xml.xpath('//message')[0]
        msg = dict((e.tag, e.text) for e in node)
        assert (data['diaspora_handle'] == c_from.diasp.username)
        assert (msg['diaspora_handle'] == c_from.diasp.username)
        assert (cls.valid_signature(c_from, msg['author_signature'], node))
        assert (cls.valid_signature(c_from, msg['parent_author_signature'],
                                    node))
        assert (data['guid'] == msg['parent_guid'])
        assert (data['guid'] == msg['conversation_guid'])

        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=c_from, created_at=created)
        part_tags = [t for t in ('subject', 'text') if t in data or t in msg]
        for order, tag in enumerate(part_tags):
            p.add_part(MimePart(
                type='text/x-markdown' if tag == 'text' else 'text/plain',
                body=(data.get(tag, None) or msg[tag]).encode('utf-8'),
            ),
                       order=order,
                       inline=True)
        p.tags = cls.find_tags(msg['text'])
        p.share_with([c_from, u_to.contact])
        p.thread_modified()
        p.diasp = DiasporaPost(guid=data['guid'], type='private')
        db.session.add(p)
        db.session.commit()
Esempio n. 3
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        node = xml.xpath('//message')[0]
        msg = dict((e.tag, e.text) for e in node)
        assert(data['diaspora_handle'] == c_from.diasp.username)
        assert(msg['diaspora_handle'] == c_from.diasp.username)
        assert(cls.valid_signature(c_from, msg['author_signature'], node))
        assert(cls.valid_signature(
            c_from, msg['parent_author_signature'], node
        ))
        assert(data['guid'] == msg['parent_guid'])
        assert(data['guid'] == msg['conversation_guid'])

        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=c_from, created_at=created)
        part_tags = [t for t in ('subject', 'text') if t in data or t in msg]
        for order, tag in enumerate(part_tags):
            p.add_part(MimePart(
                type='text/x-markdown' if tag == 'text' else 'text/plain',
                body=(data.get(tag, None) or msg[tag]).encode('utf-8'),
            ), order=order, inline=True)
        p.tags = cls.find_tags(msg['text'])
        p.share_with([c_from, u_to.contact])
        p.thread_modified()
        p.diasp = DiasporaPost(guid=data['guid'], type='private')
        db.session.add(p)
        db.session.commit()
Esempio n. 4
0
    def import_public_posts(self):
        """
        Load the JSON of public posts for this user and create local posts
        from them.
        """
        url = self.server + 'people/{0}'.format(self.guid)
        req = Request(url)
        req.add_header('User-Agent', USER_AGENT)
        req.add_header('Accept', 'application/json')
        entries = json_load(urlopen(req, timeout=10))
        if isinstance(entries, dict):
            return  # Faulty node?
        for entry in entries:
            user_guid = entry['author']['guid']
            username = entry['author']['diaspora_id']
            user = self if self.username == username \
                else DiasporaContact.get_by_username(username, commit=False)
            if not user or user.guid != user_guid:
                continue
            post_guid = entry['guid']
            existing_post = DiasporaPost.get_by_guid(post_guid)
            if existing_post or not entry['public']:
                continue  # Already imported

            if entry.get('root'):
                root_guid = entry['root']['guid']
                root_post = DiasporaPost.get_by_guid(root_guid)
                if root_post:
                    parent = root_post.post
                else:
                    continue  # Cannot find parent
            else:
                parent = None

            post = Post(author=user.contact, parent=parent)
            db.session.add(post)

            post.created_at = datetime.strptime(
                entry['created_at'],
                '%Y-%m-%dT%H:%M:%SZ'
            )
            post.thread_modified(when=datetime.strptime(
                entry['interacted_at'],
                '%Y-%m-%dT%H:%M:%SZ'
            ))
            post.add_part(
                MimePart(
                    type='text/x-markdown',
                    body=entry['text'].encode('utf-8'),
                    text_preview=entry['text']
                ),
                inline=True,
                order=0
            )
            post.share_with([user.contact], show_on_wall=True)
            post.diasp = DiasporaPost(guid=post_guid, type='public')
Esempio n. 5
0
    def import_public_posts(self):
        """
        Load the JSON of public posts for this user and create local posts
        from them.
        """
        url = self.server + 'people/{0}'.format(self.guid)
        req = Request(url)
        req.add_header('User-Agent', USER_AGENT)
        req.add_header('Accept', 'application/json')
        entries = json_load(urlopen(req, timeout=10))
        if isinstance(entries, dict):
            return  # Faulty node?
        for entry in entries:
            user_guid = entry['author']['guid']
            username = entry['author']['diaspora_id']
            user = self if self.username == username \
                else DiasporaContact.get_by_username(username, commit=False)
            if not user or user.guid != user_guid:
                continue
            post_guid = entry['guid']
            existing_post = DiasporaPost.get_by_guid(post_guid)
            if existing_post or not entry['public']:
                continue  # Already imported

            if entry.get('root'):
                root_guid = entry['root']['guid']
                root_post = DiasporaPost.get_by_guid(root_guid)
                if root_post:
                    parent = root_post.post
                else:
                    continue  # Cannot find parent
            else:
                parent = None

            post = Post(author=user.contact, parent=parent)
            db.session.add(post)

            post.created_at = datetime.strptime(entry['created_at'],
                                                '%Y-%m-%dT%H:%M:%SZ')
            post.thread_modified(when=datetime.strptime(
                entry['interacted_at'], '%Y-%m-%dT%H:%M:%SZ'))
            post.add_part(MimePart(type='text/x-markdown',
                                   body=entry['text'].encode('utf-8'),
                                   text_preview=entry['text']),
                          inline=True,
                          order=0)
            post.share_with([user.contact], show_on_wall=True)
            post.diasp = DiasporaPost(guid=post_guid, type='public')
Esempio n. 6
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(
            data['diaspora_handle'], True, False
        )
        assert(author)
        author = author.contact
        parent = DiasporaPost.get_by_guid(data['parent_guid']).post
        if not parent:
            raise TryLater()
        assert(parent.shared_with(c_from))
        assert(parent.shared_with(u_to))
        node = xml[0][0]
        if 'parent_author_signature' in data:
            assert(cls.valid_signature(
                parent.author, data['parent_author_signature'], node
            ))
            if not current_app.config.get('ALLOW_INSECURE_COMPAT', False):
                assert(
                    cls.valid_signature(author, data['author_signature'], node)
                )
        else:
            assert(cls.valid_signature(author, data['author_signature'], node))

        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=author, created_at=created, parent=parent)
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['text'].encode('utf-8'),
        ), order=0, inline=True)
        p.tags = cls.find_tags(data['text'])
        p.share_with([s.contact for s in p.root().shares])
        p.thread_modified()
        p.diasp = DiasporaPost(guid=data['guid'], type='private')
        db.session.add(p)
        db.session.commit()

        if not(u_to) or (p.parent.author_id == u_to.contact.id):
            # If the parent has signed this then it must have already been
            # via the hub.
            if 'parent_author_signature' not in data:
                cls.forward(u_to, p, node)
Esempio n. 7
0
def hide(post_id, _user):
    """
    Hide an existing Post from the user's wall and profile.
    """
    post = Post.get(post_id)
    if not post:
        abort(404, 'No such post', force_status=True)

    post.hide(_user)
    db.session.commit()

    return redirect(url_for('feed.view', _external=True))
Esempio n. 8
0
def hide(post_id, _user):
    """
    Hide an existing Post from the user's wall and profile.
    """
    post = Post.get(post_id)
    if not post:
        abort(404, 'No such post', force_status=True)

    post.hide(_user)
    db.session.commit()

    return redirect(url_for('feed.view', _external=True))
Esempio n. 9
0
def comment(post_id, _user):
    """
    Comment on (reply to) an existing Post.
    """
    post = Post.get(post_id)
    if not post:
        abort(404, 'No such post', force_status=True)
    if not post.has_permission_to_view(_user.contact):
        abort(403, 'Forbidden')

    data = _base_create_form(_user, post)

    data.update({
        'relationship': {
            'type': 'comment',
            'object': json_post(post, children=False),
            'description': 'Comment on this item'
        }
    })

    return render_response('posts_create_form.tpl', data)
Esempio n. 10
0
def comment(post_id, _user):
    """
    Comment on (reply to) an existing Post.
    """
    post = Post.get(post_id)
    if not post:
        abort(404, 'No such post', force_status=True)
    if not post.has_permission_to_view(_user.contact):
        abort(403, 'Forbidden')

    data = _base_create_form(_user, post)

    data.update({
        'relationship': {
            'type': 'comment',
            'object': json_post(post, children=False),
            'description': 'Comment on this item'
        }
    })

    return render_response('posts_create_form.tpl', data)
Esempio n. 11
0
def share(post_id, _user):
    """
    Form to share an existing Post with more Contacts.
    """
    post = Post.get(post_id)
    if not post:
        abort(404, 'No such post', force_status=True)
    if not post.has_permission_to_view(_user.contact):
        abort(403, 'Forbidden')

    data = _base_create_form(_user)

    data.update({
        'relationship': {
            'type': 'share',
            'object': json_post(post, children=False),
            'description': 'Share this item'
        },
        'default_target': {
            'type': 'all_friends',
            'id': None
        }
    })
    return render_response('posts_create_form.tpl', data)
Esempio n. 12
0
def share(post_id, _user):
    """
    Form to share an existing Post with more Contacts.
    """
    post = Post.get(post_id)
    if not post:
        abort(404, 'No such post', force_status=True)
    if not post.has_permission_to_view(_user.contact):
        abort(403, 'Forbidden')

    data = _base_create_form(_user)

    data.update({
        'relationship': {
            'type': 'share',
            'object': json_post(post, children=False),
            'description': 'Share this item'
        },
        'default_target': {
            'type': 'all_friends',
            'id': None
        }
    })
    return render_response('posts_create_form.tpl', data)
Esempio n. 13
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        shared = DiasporaPost.get_by_guid(data['root_guid'])
        assert(shared)
        shared = shared.post
        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        post = Post(author=c_from, created_at=created)
        share_part = MimePart(
            type='application/x-pyaspora-share',
            body=dumps({
                'post': {'id': shared.id},
                'author': {
                    'id': shared.author_id,
                    'name': shared.author.realname,
                }
            }).encode('utf-8'),
            text_preview="shared {0}'s post".format(shared.author.realname)
        )
        post.add_part(share_part, order=0, inline=True)
        order = 0
        for part in shared.parts:
            if part.mime_part.type != 'application/x-pyaspora-share':
                order += 1
                post.add_part(part.mime_part, inline=part.inline, order=order)
        if not post.tags:
            post.tags = shared.tags
        if u_to:
            post.share_with([c_from, u_to.contact])
        else:
            post.share_with([c_from], show_on_wall=True)
        post.thread_modified()

        post.diasp = DiasporaPost(
            guid=data['guid'],
            type='limited' if u_to else 'public'
        )
        db.session.add(post)
        db.session.commit()
Esempio n. 14
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(
            data['diaspora_handle'], True, False
        )
        assert(author)
        author = author.contact
        parent = DiasporaPost.get_by_guid(data['parent_guid']).post
        assert(parent)
        assert(parent.shared_with(c_from))
        assert(parent.shared_with(u_to))
        node = xml[0][0]
        assert(cls.valid_signature(author, data['author_signature'], node))
        if 'parent_author_signature' in data:
            assert(cls.valid_signature(
                parent.author, data['parent_author_signature'], node
            ))

        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=author, created_at=created)
        p.parent = parent
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['text'].encode('utf-8'),
        ), order=0, inline=True)
        p.tags = cls.find_tags(data['text'])
        p.share_with([s.contact for s in p.root().shares])
        p.thread_modified()
        p.diasp = DiasporaPost(guid=data['guid'], type='private')
        db.session.add(p)
        db.session.commit()

        if not(u_to) or (p.parent.author_id == u_to.contact.id):
            cls.forward(u_to, p, node)
Esempio n. 15
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(
            data['diaspora_handle'], True, False
        )
        assert(author)
        author = author.contact
        parent = DiasporaPost.get_by_guid(data['parent_guid']).post
        assert(parent)
        if u_to:
            assert(parent.shared_with(c_from))
            assert(parent.shared_with(u_to))
        node = xml[0][0]
        assert(cls.valid_signature(author, data['author_signature'], node))
        if 'parent_author_signature' in data:
            assert(
                cls.valid_signature(
                    parent.root().author, data['parent_author_signature'], node
                )
            )

        p = Post(author=author)
        p.parent = parent
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['text'].encode('utf-8'),
        ), order=0, inline=True)
        p.tags = cls.find_tags(data['text'])
        if u_to:
            p.share_with([p.author, u_to.contact])
        else:
            p.share_with([p.author], show_on_wall=True)
        if p.author.id != c_from.id:
            p.share_with([c_from])

        p.thread_modified()

        p.diasp = DiasporaPost(
            guid=data['guid'],
            type='limited' if u_to else 'public'
        )
        db.session.add(p)
        db.session.commit()

        if not(u_to) or (p.parent.author_id == u_to.contact.id):
            cls.forward(u_to, p, node)
Esempio n. 16
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        public = (data['public'] == 'true')
        assert(public or u_to)
        assert(data['diaspora_handle'] == c_from.diasp.username)
        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=c_from, created_at=created)
        msg = data.get('raw_message', None)
        if msg is None:
            msg = data.get('photo', None)
        if msg is None:
            msg = ''
        p.add_part(MimePart(
            type='text/x-markdown',
            body=msg.encode('utf-8'),
        ), order=0, inline=True)
        p.tags = cls.find_tags(msg)

        if 'poll' in data:
            pd = xml.xpath('//poll')[0]
            part = MimePart(
                type='application/x-diaspora-poll-question',
                body=pd.xpath('./question')[0].text.encode('utf-8')
            )
            part.diasp = DiasporaPart(guid=pd.xpath('./guid')[0].text)
            p.add_part(part, order=1, inline=True)
            for pos, answer in enumerate(pd.xpath('./poll_answer')):
                part = MimePart(
                    type='application/x-diaspora-poll-answer',
                    body=answer.xpath('./answer')[0].text.encode('utf-8')
                )
                part.diasp = DiasporaPart(guid=answer.xpath('./guid')[0].text)
                p.add_part(part, order=2+pos, inline=True)

        if public:
            p.share_with([c_from], show_on_wall=True)
        else:
            p.share_with([c_from])
            if u_to.contact.subscribed_to(c_from):
                p.share_with([u_to.contact])
        p.thread_modified()

        p.diasp = DiasporaPost(
            guid=data['guid'],
            type='public' if public else 'limited'
        )
        db.session.commit()
Esempio n. 17
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        shared = DiasporaPost.get_by_guid(data['root_guid'])
        if not shared:
            # Try to pull it from the Atom feed
            author = DiasporaContact.get_by_username(
                data['root_diaspora_id'], True, True
            )
            if not author:
                raise TryLater()
            author.import_public_posts()
            shared = DiasporaPost.get_by_guid(data['root_guid'])

        if not shared:
            # Fall back to poking the origin server
            post_url = urljoin(author.server, "/p/{0}.xml".format(
                data['root_guid']
            ))
            resp = urlopen(post_url, timeout=10)
            current_app.logger.debug(
                'Injecting downloaded message into processing loop'
            )
            process_incoming_message(resp.read(), author.contact, None)
            shared = DiasporaPost.get_by_guid(data['root_guid'])

        if not shared:
            # Failed
            current_app.logger.warning(
                'Could not find post being reshared (with GUID {0})'.format(
                    data['root_guid']
                )
            )
            raise TryLater()
        shared = shared.post
        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        post = Post(author=c_from, created_at=created)
        share_part = MimePart(
            type='application/x-pyaspora-share',
            body=dumps({
                'post': {'id': shared.id},
                'author': {
                    'id': shared.author_id,
                    'name': shared.author.realname,
                }
            }).encode('utf-8'),
            text_preview=u"shared {0}'s post".format(shared.author.realname)
        )
        post.add_part(share_part, order=0, inline=True)
        order = 0
        for part in shared.parts:
            if part.mime_part.type != 'application/x-pyaspora-share':
                order += 1
                post.add_part(part.mime_part, inline=part.inline, order=order)
        if not post.tags:
            post.tags = shared.tags
        if u_to:
            post.share_with([c_from])
            if u_to.contact.subscribed_to(c_from):
                p.share_with([u_to.contact])
        else:
            post.share_with([c_from], show_on_wall=True)
        post.thread_modified()

        post.diasp = DiasporaPost(
            guid=data['guid'],
            type='limited' if u_to else 'public'
        )
        db.session.add(post)
        db.session.commit()
Esempio n. 18
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(
            data['diaspora_handle'], True, False
        )
        assert(author)
        author = author.contact
        poll_part = DiasporaPart.get_by_guid(data['parent_guid'])
        if not poll_part:
            raise TryLater()
        posts = dict((p.post.id, p.post) for p in poll_part.part.posts)
        if not posts:
            raise TryLater()

        answer_part = DiasporaPart.get_by_guid(data['poll_answer_guid'])
        assert answer_part, 'Poll participation must have stored answer'

        new_part = MimePart(
            type='application/x-diaspora-poll-participation',
            body=dumps({
                'poll_guid': data['parent_guid'],
                'answer_guid': data['poll_answer_guid'],
                'answer_text': answer_part.part.body.decode('utf-8')
            })
        )

        node = xml[0][0]
        assert(cls.valid_signature(author, data['author_signature'], node))

        saved = []
        for parent in posts.values():
            # FIXME: we should validate parent_author_signature against, err
            # the right post.
            if u_to and not parent.shared_with(c_from):
                continue
            p = Post(author=author, parent=parent)
            saved.append(p)
            p.add_part(new_part, order=0, inline=True)

            if u_to:
                p.share_with([p.author])
                if parent.shared_with(u_to.contact):
                    p.share_with([u_to.contact])
            else:
                p.share_with([p.author], show_on_wall=True)
            if p.author.id != c_from.id:
                p.share_with([c_from])

            p.thread_modified()

            p.diasp = DiasporaPost(
                guid=data['guid'],
                type='limited' if u_to else 'public'
            )
            db.session.add(p)

        db.session.commit()

        for p in saved:
            if not(u_to) or (p.parent.author_id == u_to.contact.id):
                # If the parent has signed this then it must have already been
                # via the hub.
                if 'parent_author_signature' not in data:
                    cls.forward(u_to, p, node)
Esempio n. 19
0
def edit(_user):
    """
    Apply the changes from the user edit form. This updates such varied things
    as the profile photo and bio, the email address, name, password and
    interests.
    """
    from pyaspora.post.models import Post

    p = Post(author=_user.contact)
    changed = []
    order = 0

    notif_freq = post_param(
        'notification_frequency_hours',
        template='users_edit.tpl',
        optional=True
    )
    _user.notification_hours = int(notif_freq) if notif_freq else None

    email = post_param('email', optional=True)
    if email and email != _user.email:
        _user.email = email

    old_pw = post_param('current_password', optional=True)
    new_pw1 = post_param('new_password', optional=True)
    new_pw2 = post_param('new_password2', optional=True)
    if old_pw and new_pw1 and new_pw2:
        if new_pw1 != new_pw2:
            abort(400, 'New passwords do not match')
        try:
            _user.change_password(old_pw, new_pw1)
        except ValueError:
            abort(400, 'Old password is incorrect')
    db.session.add(_user)

    attachment = request.files.get('avatar', None)
    if attachment and attachment.filename:
        changed.append('avatar')
        order += 1
        check_attachment_is_safe(attachment)

        if not renderer_exists(attachment.mimetype) or \
                not attachment.mimetype.startswith('image/'):
            abort(400, 'Avatar format unsupported')

        attachment_part = MimePart(
            type=attachment.mimetype,
            body=attachment.stream.read(),
            text_preview=attachment.filename
        )

        p.add_part(attachment_part, order=order, inline=True)
        _user.contact.avatar = attachment_part

    name = post_param('name', template='users_edit.tpl', optional=True)
    if name and name != _user.contact.realname:
        _user.contact.realname = name
        changed.append('name')

    bio = post_param('bio', template='users_edit.tpl', optional=True)
    if bio:
        bio = bio.encode('utf-8')
    else:
        bio = b''
    if bio and (not _user.contact.bio or _user.contact.bio.body != bio):
        changed.append('bio')
        order += 1
        bio_part = MimePart(body=bio, type='text/plain', text_preview=None)
        p.add_part(
            order=order,
            inline=True,
            mime_part=bio_part
        )
        _user.contact.bio = bio_part

    tags = post_param('tags', optional=True)
    if tags is not None:
        tag_objects = Tag.parse_line(tags, create=True)
        old_tags = set([t.id for t in _user.contact.interests])
        new_tags = set([t.id for t in tag_objects])
        if old_tags != new_tags:
            changed.append('tags')
            _user.contact.interests = tag_objects

    p.add_part(
        order=0,
        inline=True,
        mime_part=MimePart(
            body=json_dumps({
                'fields_changed': changed
            }).encode('utf-8'),
            type='application/x-pyaspora-profile-update',
            text_preview='updated their profile'
        )
    )

    if changed:
        db.session.add(p)
        db.session.add(_user.contact)
        p.share_with([_user.contact])
        p.thread_modified()

    db.session.commit()

    return redirect(url_for('contacts.profile', contact_id=_user.contact.id))
Esempio n. 20
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        public = (data['public'] == 'true')
        assert (public or u_to)
        assert (data['diaspora_handle'] == c_from.diasp.username)
        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=c_from, created_at=created)
        msg = data.get('raw_message', None)
        if msg is None:
            msg = data.get('photo', None)
        if msg is None:
            msg = ''
        p.add_part(MimePart(
            type='text/x-markdown',
            body=msg.encode('utf-8'),
        ),
                   order=0,
                   inline=True)
        p.tags = cls.find_tags(msg)

        if 'poll' in data:
            pd = xml.xpath('//poll')[0]
            part = MimePart(
                type='application/x-diaspora-poll-question',
                body=pd.xpath('./question')[0].text.encode('utf-8'))
            part.diasp = DiasporaPart(guid=pd.xpath('./guid')[0].text)
            p.add_part(part, order=1, inline=True)
            for pos, answer in enumerate(pd.xpath('./poll_answer')):
                part = MimePart(
                    type='application/x-diaspora-poll-answer',
                    body=answer.xpath('./answer')[0].text.encode('utf-8'))
                part.diasp = DiasporaPart(guid=answer.xpath('./guid')[0].text)
                p.add_part(part, order=2 + pos, inline=True)

        if public:
            p.share_with([c_from], show_on_wall=True)
        else:
            p.share_with([c_from])
            if u_to.contact.subscribed_to(c_from):
                p.share_with([u_to.contact])
        p.thread_modified()

        p.diasp = DiasporaPost(guid=data['guid'],
                               type='public' if public else 'limited')
        db.session.commit()
Esempio n. 21
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        public = (data['public'] == 'true')
        assert (public or u_to)
        assert (data['diaspora_handle'] == c_from.diasp.username)
        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=c_from, created_at=created)
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['raw_message'].encode('utf-8'),
        ),
                   order=0,
                   inline=True)
        p.tags = cls.find_tags(data['raw_message'])
        if public:
            p.share_with([c_from], show_on_wall=True)
        else:
            p.share_with([c_from, u_to.contact])
        p.thread_modified()

        p.diasp = DiasporaPost(guid=data['guid'],
                               type='public' if public else 'limited')
        db.session.commit()
Esempio n. 22
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(data['diaspora_handle'], True,
                                                 False)
        assert (author)
        author = author.contact
        poll_part = DiasporaPart.get_by_guid(data['parent_guid'])
        if not poll_part:
            raise TryLater()
        posts = dict((p.post.id, p.post) for p in poll_part.part.posts)
        if not posts:
            raise TryLater()

        answer_part = DiasporaPart.get_by_guid(data['poll_answer_guid'])
        assert answer_part, 'Poll participation must have stored answer'

        new_part = MimePart(type='application/x-diaspora-poll-participation',
                            body=dumps({
                                'poll_guid':
                                data['parent_guid'],
                                'answer_guid':
                                data['poll_answer_guid'],
                                'answer_text':
                                answer_part.part.body.decode('utf-8')
                            }))

        node = xml[0][0]
        assert (cls.valid_signature(author, data['author_signature'], node))

        saved = []
        for parent in posts.values():
            # FIXME: we should validate parent_author_signature against, err
            # the right post.
            if u_to and not parent.shared_with(c_from):
                continue
            p = Post(author=author, parent=parent)
            saved.append(p)
            p.add_part(new_part, order=0, inline=True)

            if u_to:
                p.share_with([p.author])
                if parent.shared_with(u_to.contact):
                    p.share_with([u_to.contact])
            else:
                p.share_with([p.author], show_on_wall=True)
            if p.author.id != c_from.id:
                p.share_with([c_from])

            p.thread_modified()

            p.diasp = DiasporaPost(guid=data['guid'],
                                   type='limited' if u_to else 'public')
            db.session.add(p)

        db.session.commit()

        for p in saved:
            if not (u_to) or (p.parent.author_id == u_to.contact.id):
                # If the parent has signed this then it must have already been
                # via the hub.
                if 'parent_author_signature' not in data:
                    cls.forward(u_to, p, node)
Esempio n. 23
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        shared = DiasporaPost.get_by_guid(data['root_guid'])
        if not shared:
            # Try to pull it from the Atom feed
            author = DiasporaContact.get_by_username(data['root_diaspora_id'],
                                                     True, True)
            if not author:
                raise TryLater()
            author.import_public_posts()
            shared = DiasporaPost.get_by_guid(data['root_guid'])

        if not shared:
            # Fall back to poking the origin server
            post_url = urljoin(author.server,
                               "/p/{0}.xml".format(data['root_guid']))
            resp = urlopen(post_url, timeout=10)
            current_app.logger.debug(
                'Injecting downloaded message into processing loop')
            process_incoming_message(resp.read(), author.contact, None)
            shared = DiasporaPost.get_by_guid(data['root_guid'])

        if not shared:
            # Failed
            current_app.logger.warning(
                'Could not find post being reshared (with GUID {0})'.format(
                    data['root_guid']))
            raise TryLater()
        shared = shared.post
        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        post = Post(author=c_from, created_at=created)
        share_part = MimePart(type='application/x-pyaspora-share',
                              body=dumps({
                                  'post': {
                                      'id': shared.id
                                  },
                                  'author': {
                                      'id': shared.author_id,
                                      'name': shared.author.realname,
                                  }
                              }).encode('utf-8'),
                              text_preview=u"shared {0}'s post".format(
                                  shared.author.realname))
        post.add_part(share_part, order=0, inline=True)
        order = 0
        for part in shared.parts:
            if part.mime_part.type != 'application/x-pyaspora-share':
                order += 1
                post.add_part(part.mime_part, inline=part.inline, order=order)
        if not post.tags:
            post.tags = shared.tags
        if u_to:
            post.share_with([c_from])
            if u_to.contact.subscribed_to(c_from):
                p.share_with([u_to.contact])
        else:
            post.share_with([c_from], show_on_wall=True)
        post.thread_modified()

        post.diasp = DiasporaPost(guid=data['guid'],
                                  type='limited' if u_to else 'public')
        db.session.add(post)
        db.session.commit()
Esempio n. 24
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(data['diaspora_handle'], True,
                                                 False)
        assert (author)
        author = author.contact
        parent = DiasporaPost.get_by_guid(data['parent_guid']).post
        if not parent:
            raise TryLater()
        assert (parent.shared_with(c_from))
        assert (parent.shared_with(u_to))
        node = xml[0][0]
        if 'parent_author_signature' in data:
            assert (cls.valid_signature(parent.author,
                                        data['parent_author_signature'], node))
            if not current_app.config.get('ALLOW_INSECURE_COMPAT', False):
                assert (cls.valid_signature(author, data['author_signature'],
                                            node))
        else:
            assert (cls.valid_signature(author, data['author_signature'],
                                        node))

        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=author, created_at=created, parent=parent)
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['text'].encode('utf-8'),
        ),
                   order=0,
                   inline=True)
        p.tags = cls.find_tags(data['text'])
        p.share_with([s.contact for s in p.root().shares])
        p.thread_modified()
        p.diasp = DiasporaPost(guid=data['guid'], type='private')
        db.session.add(p)
        db.session.commit()

        if not (u_to) or (p.parent.author_id == u_to.contact.id):
            # If the parent has signed this then it must have already been
            # via the hub.
            if 'parent_author_signature' not in data:
                cls.forward(u_to, p, node)
Esempio n. 25
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(data['diaspora_handle'], True,
                                                 False)
        assert (author)
        author = author.contact
        parent = DiasporaPost.get_by_guid(data['parent_guid'])

        # Which post is this in reply to?
        if parent:
            parent = parent.post
        else:
            raise TryLater()

        if u_to:
            assert (parent.shared_with(c_from))
        node = xml[0][0]
        if 'parent_author_signature' in data:
            assert (cls.valid_signature(parent.root().author,
                                        data['parent_author_signature'], node))
            if not current_app.config.get('ALLOW_INSECURE_COMPAT', False):
                assert (cls.valid_signature(author, data['author_signature'],
                                            node))
        else:
            assert (cls.valid_signature(author, data['author_signature'],
                                        node))

        p = Post(author=author, parent=parent)
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['text'].encode('utf-8'),
        ),
                   order=0,
                   inline=True)
        p.tags = cls.find_tags(data['text'])
        if u_to:
            p.share_with([p.author])
            if parent.shared_with(u_to.contact):
                p.share_with([u_to.contact])
        else:
            p.share_with([p.author], show_on_wall=True)
        if p.author.id != c_from.id:
            p.share_with([c_from])

        p.thread_modified()

        p.diasp = DiasporaPost(guid=data['guid'],
                               type='limited' if u_to else 'public')
        db.session.add(p)
        db.session.commit()

        if not (u_to) or (p.parent.author_id == u_to.contact.id):
            # If the parent has signed this then it must have already been
            # via the hub.
            if 'parent_author_signature' not in data:
                cls.forward(u_to, p, node)
Esempio n. 26
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(data['diaspora_handle'], True,
                                                 False)
        assert (author)
        author = author.contact
        parent = DiasporaPost.get_by_guid(data['parent_guid']).post
        assert (parent)
        if u_to:
            assert (parent.shared_with(c_from))
            assert (parent.shared_with(u_to))
        node = xml[0][0]
        assert (cls.valid_signature(author, data['author_signature'], node))
        if 'parent_author_signature' in data:
            assert (cls.valid_signature(parent.root().author,
                                        data['parent_author_signature'], node))

        p = Post(author=author)
        p.parent = parent
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['text'].encode('utf-8'),
        ),
                   order=0,
                   inline=True)
        p.tags = cls.find_tags(data['text'])
        if u_to:
            p.share_with([p.author, u_to.contact])
        else:
            p.share_with([p.author], show_on_wall=True)
        if p.author.id != c_from.id:
            p.share_with([c_from])

        p.thread_modified()

        p.diasp = DiasporaPost(guid=data['guid'],
                               type='limited' if u_to else 'public')
        db.session.add(p)
        db.session.commit()

        if not (u_to) or (p.parent.author_id == u_to.contact.id):
            cls.forward(u_to, p, node)
Esempio n. 27
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(data['diaspora_handle'], True,
                                                 False)
        assert (author)
        author = author.contact
        parent = DiasporaPost.get_by_guid(data['parent_guid']).post
        assert (parent)
        assert (parent.shared_with(c_from))
        assert (parent.shared_with(u_to))
        node = xml[0][0]
        assert (cls.valid_signature(author, data['author_signature'], node))
        if 'parent_author_signature' in data:
            assert (cls.valid_signature(parent.author,
                                        data['parent_author_signature'], node))

        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=author, created_at=created)
        p.parent = parent
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['text'].encode('utf-8'),
        ),
                   order=0,
                   inline=True)
        p.tags = cls.find_tags(data['text'])
        p.share_with([s.contact for s in p.root().shares])
        p.thread_modified()
        p.diasp = DiasporaPost(guid=data['guid'], type='private')
        db.session.add(p)
        db.session.commit()

        if not (u_to) or (p.parent.author_id == u_to.contact.id):
            cls.forward(u_to, p, node)
Esempio n. 28
0
def create(_user):
    """
    Create a new Post and Share it with the selected Contacts.
    """
    body = post_param('body')
    relationship = {
        'type': post_param('relationship_type', optional=True),
        'id': post_param('relationship_id', optional=True),
    }

    target = {
        'type': post_param('target_type'),
        'id': post_param('target_id', optional=True),
    }

    assert (target['type'] in targets_by_name)

    # Loathe inflexible HTML forms
    if target['id'] is None:
        target['id'] = post_param('target_%s_id' % target['type'],
                                  optional=True)

    if relationship['type']:
        post = Post.get(relationship['id'])
        if not post:
            abort(404, 'No such post', force_status=True)
        if not post.has_permission_to_view(_user.contact):
            abort(403, 'Forbidden')
        relationship['post'] = post

    shared = None
    post = Post(author=_user.contact)
    body_part = MimePart(type='text/x-markdown',
                         body=body.encode('utf-8'),
                         text_preview=None)

    topics = post_param('tags', optional=True)
    if topics:
        post.tags = Tag.parse_line(topics, create=True)

    if relationship['type'] == 'comment':
        post.parent = relationship['post']
        post.add_part(body_part, order=0, inline=True)
    elif relationship['type'] == 'share':
        shared = relationship['post']
        share_part = MimePart(type='application/x-pyaspora-share',
                              body=dumps({
                                  'post': {
                                      'id': shared.id
                                  },
                                  'author': {
                                      'id': shared.author_id,
                                      'name': shared.author.realname,
                                  }
                              }).encode('utf-8'),
                              text_preview=u"shared {0}'s post".format(
                                  shared.author.realname))
        post.add_part(share_part, order=0, inline=True)
        post.add_part(body_part, order=1, inline=True)
        order = 1
        for part in shared.parts:
            if part.mime_part.type != 'application/x-pyaspora-share':
                order += 1
                post.add_part(part.mime_part, inline=part.inline, order=order)
        if not post.tags:
            post.tags = shared.tags
    else:  # Naked post
        post.add_part(body_part, order=0, inline=True)
        attachment = request.files.get('attachment', None)
        if attachment and attachment.filename:
            check_attachment_is_safe(attachment)
            attachment_part = MimePart(type=attachment.mimetype,
                                       body=attachment.stream.read(),
                                       text_preview=attachment.filename)
            post.add_part(attachment_part,
                          order=1,
                          inline=bool(renderer_exists(attachment.mimetype)))

    post.thread_modified()

    # Sigh, need an ID for the post for making shares
    db.session.add(post)
    db.session.commit()

    targets_by_name[target['type']].make_shares(post,
                                                target['id'],
                                                reshare_of=shared)
    db.session.commit()

    data = json_post(post)
    return redirect(url_for('feed.view', _external=True), data_structure=data)
Esempio n. 29
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        shared = DiasporaPost.get_by_guid(data['root_guid'])
        assert (shared)
        shared = shared.post
        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        post = Post(author=c_from, created_at=created)
        share_part = MimePart(type='application/x-pyaspora-share',
                              body=dumps({
                                  'post': {
                                      'id': shared.id
                                  },
                                  'author': {
                                      'id': shared.author_id,
                                      'name': shared.author.realname,
                                  }
                              }).encode('utf-8'),
                              text_preview="shared {0}'s post".format(
                                  shared.author.realname))
        post.add_part(share_part, order=0, inline=True)
        order = 0
        for part in shared.parts:
            if part.mime_part.type != 'application/x-pyaspora-share':
                order += 1
                post.add_part(part.mime_part, inline=part.inline, order=order)
        if not post.tags:
            post.tags = shared.tags
        if u_to:
            post.share_with([c_from, u_to.contact])
        else:
            post.share_with([c_from], show_on_wall=True)
        post.thread_modified()

        post.diasp = DiasporaPost(guid=data['guid'],
                                  type='limited' if u_to else 'public')
        db.session.add(post)
        db.session.commit()
Esempio n. 30
0
def create(_user):
    """
    Create a new Post and Share it with the selected Contacts.
    """
    body = post_param('body')
    relationship = {
        'type': post_param('relationship_type', optional=True),
        'id': post_param('relationship_id', optional=True),
    }

    target = {
        'type': post_param('target_type'),
        'id': post_param('target_id', optional=True),
    }

    assert(target['type'] in targets_by_name)

    # Loathe inflexible HTML forms
    if target['id'] is None:
        target['id'] = post_param(
            'target_%s_id' % target['type'], optional=True)

    if relationship['type']:
        post = Post.get(relationship['id'])
        if not post:
            abort(404, 'No such post', force_status=True)
        if not post.has_permission_to_view(_user.contact):
            abort(403, 'Forbidden')
        relationship['post'] = post

    post = Post(author=_user.contact)
    body_part = MimePart(type='text/x-markdown', body=body.encode('utf-8'),
                         text_preview=None)

    topics = post_param('tags', optional=True)
    if topics:
        post.tags = Tag.parse_line(topics, create=True)

    if relationship['type'] == 'comment':
        post.parent = relationship['post']
        post.add_part(body_part, order=0, inline=True)
    elif relationship['type'] == 'share':
        shared = relationship['post']
        share_part = MimePart(
            type='application/x-pyaspora-share',
            body=dumps({
                'post': {'id': shared.id},
                'author': {
                    'id': shared.author_id,
                    'name': shared.author.realname,
                }
            }).encode('utf-8'),
            text_preview="shared {0}'s post".format(shared.author.realname)
        )
        post.add_part(share_part, order=0, inline=True)
        post.add_part(body_part, order=1, inline=True)
        order = 1
        for part in shared.parts:
            if part.mime_part.type != 'application/x-pyaspora-share':
                order += 1
                post.add_part(part.mime_part, inline=part.inline, order=order)
        if not post.tags:
            post.tags = shared.tags
    else:  # Naked post
        post.add_part(body_part, order=0, inline=True)
        attachment = request.files.get('attachment', None)
        if attachment and attachment.filename:
            check_attachment_is_safe(attachment)
            attachment_part = MimePart(
                type=attachment.mimetype,
                body=attachment.stream.read(),
                text_preview=attachment.filename
            )
            post.add_part(attachment_part, order=1,
                          inline=bool(renderer_exists(attachment.mimetype)))

    post.thread_modified()

    # Sigh, need an ID for the post for making shares
    db.session.add(post)
    db.session.commit()

    targets_by_name[target['type']].make_shares(post, target['id'])
    db.session.commit()

    data = json_post(post)
    return redirect(url_for('feed.view', _external=True), data_structure=data)
Esempio n. 31
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        public = (data['public'] == 'true')
        assert(public or u_to)
        assert(data['diaspora_handle'] == c_from.diasp.username)
        created = datetime.strptime(data['created_at'], '%Y-%m-%d %H:%M:%S %Z')
        p = Post(author=c_from, created_at=created)
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['raw_message'].encode('utf-8'),
        ), order=0, inline=True)
        p.tags = cls.find_tags(data['raw_message'])
        if public:
            p.share_with([c_from], show_on_wall=True)
        else:
            p.share_with([c_from, u_to.contact])
        p.thread_modified()

        p.diasp = DiasporaPost(
            guid=data['guid'],
            type='public' if public else 'limited'
        )
        db.session.commit()
Esempio n. 32
0
def edit(_user):
    """
    Apply the changes from the user edit form. This updates such varied things
    as the profile photo and bio, the email address, name, password and
    interests.
    """
    from pyaspora.post.models import Post

    p = Post(author=_user.contact)
    changed = []
    order = 0

    notif_freq = post_param(
        'notification_frequency_hours',
        template='users_edit.tpl',
        optional=True
    )
    _user.notification_hours = int(notif_freq) if notif_freq else None

    email = post_param('email', optional=True)
    if email and email != _user.email:
        _user.email = email

    old_pw = post_param('current_password', optional=True)
    new_pw1 = post_param('new_password', optional=True)
    new_pw2 = post_param('new_password2', optional=True)
    if old_pw and new_pw1 and new_pw2:
        if new_pw1 != new_pw2:
            abort(400, 'New passwords do not match')
        try:
            _user.change_password(old_pw, new_pw1)
        except ValueError:
            abort(400, 'Old password is incorrect')
    db.session.add(_user)

    attachment = request.files.get('avatar', None)
    if attachment and attachment.filename:
        changed.append('avatar')
        order += 1
        check_attachment_is_safe(attachment)

        if not renderer_exists(attachment.mimetype) or \
                not attachment.mimetype.startswith('image/'):
            abort(400, 'Avatar format unsupported')

        attachment_part = MimePart(
            type=attachment.mimetype,
            body=attachment.stream.read(),
            text_preview=attachment.filename
        )

        p.add_part(attachment_part, order=order, inline=True)
        _user.contact.avatar = attachment_part

    name = post_param('name', template='users_edit.tpl', optional=True)
    if name and name != _user.contact.realname:
        _user.contact.realname = name
        changed.append('name')

    bio = post_param('bio', template='users_edit.tpl', optional=True)
    if bio:
        bio = bio.encode('utf-8')
    else:
        bio = b''
    if bio and (not _user.contact.bio or _user.contact.bio.body != bio):
        changed.append('bio')
        order += 1
        bio_part = MimePart(body=bio, type='text/plain', text_preview=None)
        p.add_part(
            order=order,
            inline=True,
            mime_part=bio_part
        )
        _user.contact.bio = bio_part

    tags = post_param('tags', optional=True)
    if tags is not None:
        tag_objects = Tag.parse_line(tags, create=True)
        old_tags = set([t.id for t in _user.contact.interests])
        new_tags = set([t.id for t in tag_objects])
        if old_tags != new_tags:
            changed.append('tags')
            _user.contact.interests = tag_objects

    p.add_part(
        order=0,
        inline=True,
        mime_part=MimePart(
            body=json_dumps({
                'fields_changed': changed
            }).encode('utf-8'),
            type='application/x-pyaspora-profile-update',
            text_preview='updated their profile'
        )
    )

    if changed:
        db.session.add(p)
        db.session.add(_user.contact)
        p.share_with([_user.contact])
        p.thread_modified()

    db.session.commit()

    return redirect(url_for('contacts.profile', contact_id=_user.contact.id))
Esempio n. 33
0
    def receive(cls, xml, c_from, u_to):
        data = cls.as_dict(xml)
        if DiasporaPost.get_by_guid(data['guid']):
            return
        author = DiasporaContact.get_by_username(
            data['diaspora_handle'], True, False
        )
        assert(author)
        author = author.contact
        parent = DiasporaPost.get_by_guid(data['parent_guid'])

        # Which post is this in reply to?
        if parent:
            parent = parent.post
        else:
            raise TryLater()

        if u_to:
            assert(parent.shared_with(c_from))
        node = xml[0][0]
        if 'parent_author_signature' in data:
            assert(
                cls.valid_signature(
                    parent.root().author, data['parent_author_signature'], node
                )
            )
            if not current_app.config.get('ALLOW_INSECURE_COMPAT', False):
                assert(
                    cls.valid_signature(author, data['author_signature'], node)
                )
        else:
            assert(cls.valid_signature(author, data['author_signature'], node))

        p = Post(author=author, parent=parent)
        p.add_part(MimePart(
            type='text/x-markdown',
            body=data['text'].encode('utf-8'),
        ), order=0, inline=True)
        p.tags = cls.find_tags(data['text'])
        if u_to:
            p.share_with([p.author])
            if parent.shared_with(u_to.contact):
                p.share_with([u_to.contact])
        else:
            p.share_with([p.author], show_on_wall=True)
        if p.author.id != c_from.id:
            p.share_with([c_from])

        p.thread_modified()

        p.diasp = DiasporaPost(
            guid=data['guid'],
            type='limited' if u_to else 'public'
        )
        db.session.add(p)
        db.session.commit()

        if not(u_to) or (p.parent.author_id == u_to.contact.id):
            # If the parent has signed this then it must have already been
            # via the hub.
            if 'parent_author_signature' not in data:
                cls.forward(u_to, p, node)