Пример #1
0
    def format(self, thread, comment):

        rv = io.StringIO()

        author = comment["author"] or "Anonymous"
        if comment["email"]:
            author += " <%s>" % comment["email"]

        rv.write(author + " wrote:\n")
        rv.write("\n")
        rv.write(comment["text"] + "\n")

        if comment["edit"]:
            rv.write("\nand edited the article text.\n")

        rv.write("\n")

        if comment["website"]:
            rv.write("User's URL: %s\n" % comment["website"])

        rv.write("IP address: %s\n" % comment["remote_addr"])
        rv.write("Link to comment: %s\n" % (local("origin") + thread["uri"] + "#isso-%i" % comment["id"]))
        rv.write("\n")

        uri = local("host") + "/id/%i" % comment["id"]
        key = self.isso.sign(comment["id"])

        rv.write("---\n")
        rv.write("Delete comment: %s\n" % (uri + "/delete/" + key))

        if comment["mode"] == 2:
            rv.write("Activate comment: %s\n" % (uri + "/activate/" + key))

        rv.seek(0)
        return rv.read()
Пример #2
0
    def show(self, environ, request):

        rv = {
            "version": dist.version,
            "host": str(local("host")),
            "origin": str(local("origin")),
            "moderation": self.moderation,
        }

        return Response(json.dumps(rv), 200, content_type="application/json")
Пример #3
0
    def __init__(self, isso):

        self.isso = isso
        self.conf = isso.conf.section("smtp")
        self.public_endpoint = isso.conf.get("server", "public-endpoint") or local("host")
        self.admin_notify = any((n in ("smtp", "SMTP")) for n in isso.conf.getlist("general", "notify"))
        self.reply_notify = isso.conf.getboolean("general", "reply-notifications")

        # test SMTP connectivity
        try:
            with SMTPConnection(self.conf):
                logger.info("connected to SMTP server")
        except (socket.error, smtplib.SMTPException):
            logger.exception("unable to connect to SMTP server")

        if uwsgi:
            def spooler(args):
                try:
                    self._sendmail(args[b"subject"].decode("utf-8"),
                                   args["body"].decode("utf-8"),
                                   args[b"to"].decode("utf-8"))
                except smtplib.SMTPConnectError:
                    return uwsgi.SPOOL_RETRY
                else:
                    return uwsgi.SPOOL_OK

            uwsgi.spooler = spooler
Пример #4
0
    def moderate(self, environ, request, id, action, key):
        try:
            id = self.isso.unsign(key, max_age=2**32)
        except (BadSignature, SignatureExpired):
            raise Forbidden

        item = self.comments.get(id)
        thread = self.threads.get(item['tid'])
        link = local("origin") + thread["uri"] + "#isso-%i" % item["id"]

        if item is None:
            raise NotFound

        if request.method == "GET":
            modal = (
                "<!DOCTYPE html>"
                "<html>"
                "<head>"
                "<script>"
                "  if (confirm('%s: Are you sure?')) {"
                "      xhr = new XMLHttpRequest;"
                "      xhr.open('POST', window.location.href);"
                "      xhr.send(null);"
                "      xhr.onload = function() {"
                "          window.location.href = %s;"
                "      };"
                "  }"
                "</script>" % (action.capitalize(), json.dumps(link)))

            return Response(modal, 200, content_type="text/html")

        if action == "activate":
            if item['mode'] == 1:
                return Response("Already activated", 200)
            with self.isso.lock:
                self.comments.activate(id)
            self.signal("comments.activate", thread, item)
            return Response("Yo", 200)
        elif action == "edit":
            data = request.get_json()
            with self.isso.lock:
                rv = self.comments.update(id, data)
            for key in set(rv.keys()) - API.FIELDS:
                rv.pop(key)
            self.signal("comments.edit", rv)
            return JSON(rv, 200)
        else:
            with self.isso.lock:
                self.comments.delete(id)
            self.cache.delete(
                'hash', (item['email'] or item['remote_addr']).encode('utf-8'))
            self.signal("comments.delete", id)
            return Response("Yo", 200)

        """
Пример #5
0
    def moderate(self, environ, request, id, action, key):
        try:
            id = self.isso.unsign(key, max_age=2**32)
        except (BadSignature, SignatureExpired):
            raise Forbidden

        item = self.comments.get(id)
        thread = self.threads.get(item['tid'])
        link = local("origin") + thread["uri"] + "#isso-%i" % item["id"]

        if item is None:
            raise NotFound

        if request.method == "GET":
            modal = (
                "<!DOCTYPE html>"
                "<html>"
                "<head>"
                "<script>"
                "  if (confirm('%s: Are you sure?')) {"
                "      xhr = new XMLHttpRequest;"
                "      xhr.open('POST', window.location.href);"
                "      xhr.send(null);"
                "      xhr.onload = function() {"
                "          window.location.href = %s;"
                "      };"
                "  }"
                "</script>" % (action.capitalize(), json.dumps(link)))

            return Response(modal, 200, content_type="text/html")

        if action == "activate":
            if item['mode'] == 1:
                return Response("Already activated", 200)
            with self.isso.lock:
                self.comments.activate(id)
            self.signal("comments.activate", thread, item)
            return Response("Yo", 200)
        elif action == "edit":
            data = request.get_json()
            with self.isso.lock:
                rv = self.comments.update(id, data)
            for key in set(rv.keys()) - API.FIELDS:
                rv.pop(key)
            self.signal("comments.edit", rv)
            return JSON(rv, 200)
        else:
            with self.isso.lock:
                self.comments.delete(id)
            self.cache.delete(
                'hash', (item['email'] or item['remote_addr']).encode('utf-8'))
            self.signal("comments.delete", id)
            return Response("Yo", 200)

        """
Пример #6
0
    def format(self,
               thread,
               comment,
               parent_comment,
               recipient=None,
               admin=False):

        rv = io.StringIO()

        author = comment["author"] or "Anonymous"
        if admin and comment["email"]:
            author += " <%s>" % comment["email"]

        rv.write(author + " wrote:\n")
        rv.write("\n")
        rv.write(comment["text"] + "\n")
        rv.write("\n")

        if admin:
            if comment["website"]:
                rv.write("User's URL: %s\n" % comment["website"])

            rv.write("IP address: %s\n" % comment["remote_addr"])

        rv.write(
            "Link to comment: %s\n" %
            (local("origin") + thread["uri"] + "#isso-%i" % comment["id"]))
        rv.write("\n")
        rv.write("---\n")

        if admin:
            uri = self.public_endpoint + "/id/%i" % comment["id"]
            key = self.isso.sign(comment["id"])

            rv.write("Delete comment: %s\n" % (uri + "/delete/" + key))

            if comment["mode"] == 2:
                rv.write("Activate comment: %s\n" % (uri + "/activate/" + key))

        else:
            uri = self.public_endpoint + "/id/%i" % parent_comment["id"]
            key = self.isso.sign(('unsubscribe', recipient))

            rv.write("Unsubscribe from this conversation: %s\n" %
                     (uri + "/unsubscribe/" + quote(recipient) + "/" + key))

        rv.seek(0)
        return rv.read()
Пример #7
0
    def format(self, thread, comment, parent_comment, recipient=None, admin=False):

        rv = io.StringIO()

        author = comment["author"] or "Anonymous"
        if admin and comment["email"]:
            author += " <%s>" % comment["email"]

        rv.write(author + " wrote:\n")
        rv.write("\n")
        rv.write(comment["text"] + "\n")
        rv.write("\n")

        if admin:
            if comment["website"]:
                rv.write("User's URL: %s\n" % comment["website"])

            rv.write("IP address: %s\n" % comment["remote_addr"])

        rv.write("Link to comment: %s\n" %
                 (local("origin") + thread["uri"] + "#isso-%i" % comment["id"]))
        rv.write("\n")
        rv.write("---\n")

        if admin:
            uri = self.public_endpoint + "/id/%i" % comment["id"]
            key = self.isso.sign(comment["id"])

            rv.write("Delete comment: %s\n" % (uri + "/delete/" + key))

            if comment["mode"] == 2:
                rv.write("Activate comment: %s\n" % (uri + "/activate/" + key))

        else:
            uri = self.public_endpoint + "/id/%i" % parent_comment["id"]
            key = self.isso.sign(('unsubscribe', recipient))

            rv.write("Unsubscribe from this conversation: %s\n" % (uri + "/unsubscribe/" + quote(recipient) + "/" + key))

        rv.seek(0)
        return rv.read()
Пример #8
0
    def format(self, thread, comment):

        md = []

        author = comment["author"] or "Anonymous"
        if comment["email"]:
            author += " <%s>" % comment["email"]

        md.append(author + " wrote:\n")
        md.append("\n")
        md.append(comment["text"] + "\n")
        md.append("\n")

        if comment["website"]:
            md.append("User's URL: %s\n" % comment["website"])
        md.append("IP address: %s\n" % comment["remote_addr"])

        view_uri = local("origin") + thread["uri"] + "#isso-%i" % comment["id"]
        if not self.moderated:
            md.append("Link to comment: %s\n" % view_uri)
            md.append("\n")
            md.append("---\n")

        uri = self.public_endpoint + "/id/%i" % comment["id"]
        key = self.isso.sign(comment["id"])
        delete_uri = uri + "/delete/" + key
        activate_uri = uri + "/activate/" + key
        if not self.moderated:
            md.append("Delete comment: %s\n" % delete_uri)
            if comment["mode"] == 2:
                md.append("Activate comment: %s\n" % activate_uri)

        rv = "\n".join(md)
        if self.moderated:
            return dict(rv=rv, v=view_uri, d=delete_uri, a=activate_uri)
        else:
            return rv
Пример #9
0
    def format(self, thread, comment):

        rv = io.StringIO()

        author = comment["author"] or "Anonymous"
        if comment["email"]:
            author += " <%s>" % comment["email"]

        rv.write(author + " wrote:\n")
        rv.write("\n")
        rv.write(comment["text"] + "\n")
        rv.write("\n")

        if comment["website"]:
            rv.write("User's URL: %s\n" % comment["website"])

        rv.write("IP address: %s\n" % comment["remote_addr"])
        rv.write(
            "Link to comment: %s\n" %
            (local("origin") + thread["uri"] + "#isso-%i" % comment["id"]))
        rv.write("\n")

        # uri = self.general_host + "/id/%i" % comment["id"]
        name = self.isso.conf.get("general", "name")
        uri = os.environ['ISSO_DOMAIN'] + "/" + name + "/id/%i" % comment["id"]

        key = self.isso.sign(comment["id"])

        rv.write("---\n")
        rv.write("Delete comment: %s\n" % (uri + "/delete/" + key))

        if comment["mode"] == 2:
            rv.write("Activate comment: %s\n" % (uri + "/activate/" + key))

        rv.seek(0)
        return rv.read()
Пример #10
0
    def new(self, environ, request, uri):

        data = request.get_json()

        for field in set(data.keys()) - API.ACCEPT:
            data.pop(field)

        for key in ("author", "email", "website", "parent"):
            data.setdefault(key, None)

        valid, reason = API.verify(data)
        if not valid:
            return BadRequest(reason)

        for field in ("author", "email"):
            if data.get(field) is not None:
                data[field] = cgi.escape(data[field])

        data['mode'] = 2 if self.moderated else 1
        data['remote_addr'] = utils.anonymize(str(request.remote_addr))

        with self.isso.lock:
            if uri not in self.threads:
                with http.curl('GET', local("origin"), uri) as resp:
                    if resp and resp.status == 200:
                        uri, title = parse.thread(resp.read(), id=uri)
                    else:
                        return NotFound('URI does not exist')

                thread = self.threads.new(uri, title)
                self.signal("comments.new:new-thread", thread)
            else:
                thread = self.threads[uri]

        # notify extensions that the new comment is about to save
        self.signal("comments.new:before-save", thread, data)

        valid, reason = self.guard.validate(uri, data)
        if not valid:
            self.signal("comments.new:guard", reason)
            raise Forbidden(reason)

        with self.isso.lock:
            rv = self.comments.add(uri, data)

        # notify extension, that the new comment has been successfully saved
        self.signal("comments.new:after-save", thread, rv)

        cookie = functools.partial(dump_cookie,
            value=self.isso.sign([rv["id"], sha1(rv["text"])]),
            max_age=self.conf.getint('max-age'))

        rv["text"] = self.isso.render(rv["text"])
        rv["hash"] = pbkdf2(rv['email'] or rv['remote_addr'], self.isso.salt, 1000, 6).decode("utf-8")

        self.cache.set('hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash'])

        for key in set(rv.keys()) - API.FIELDS:
            rv.pop(key)

        # success!
        self.signal("comments.new:finish", thread, rv)

        resp = JSON(rv, 202 if rv["mode"] == 2 else 201)
        resp.headers.add("Set-Cookie", cookie(str(rv["id"])))
        resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"]))
        return resp
Пример #11
0
    def new(self, environ, request, uri):

        data = request.get_json()

        for field in set(data.keys()) - API.ACCEPT:
            data.pop(field)

        for key in ("author", "email", "website", "parent"):
            data.setdefault(key, None)

        valid, reason = API.verify(data)
        if not valid:
            return BadRequest(reason)

        for field in ("author", "email", "website"):
            if data.get(field) is not None:
                data[field] = cgi.escape(data[field])

        if data.get("website"):
            data["website"] = normalize(data["website"])

        data['mode'] = 2 if self.moderated else 1
        data['remote_addr'] = utils.anonymize(str(request.remote_addr))

        with self.isso.lock:
            if uri not in self.threads:
                if 'title' not in data:
                    with http.curl('GET', local("origin"), uri) as resp:
                        if resp and resp.status == 200:
                            uri, title = parse.thread(resp.read(), id=uri)
                        else:
                            return NotFound('URI does not exist %s')
                else:
                    title = data['title']

                thread = self.threads.new(uri, title)
                self.signal("comments.new:new-thread", thread)
            else:
                thread = self.threads[uri]

        # notify extensions that the new comment is about to save
        self.signal("comments.new:before-save", thread, data)

        valid, reason = self.guard.validate(uri, data)
        if not valid:
            self.signal("comments.new:guard", reason)
            raise Forbidden(reason)

        with self.isso.lock:
            rv = self.comments.add(uri, data)

        # notify extension, that the new comment has been successfully saved
        self.signal("comments.new:after-save", thread, rv)

        cookie = functools.partial(dump_cookie,
                                   value=self.isso.sign(
                                       [rv["id"], sha1(rv["text"])]),
                                   max_age=self.conf.getint('max-age'))

        rv["text"] = self.isso.render(rv["text"])
        rv["hash"] = self.hash(rv['email'] or rv['remote_addr'])

        self.cache.set(
            'hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash'])

        rv = self._add_gravatar_image(rv)

        for key in set(rv.keys()) - API.FIELDS:
            rv.pop(key)

        # success!
        self.signal("comments.new:finish", thread, rv)

        resp = JSON(rv, 202 if rv["mode"] == 2 else 201)
        resp.headers.add("Set-Cookie", cookie(str(rv["id"])))
        resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"]))
        return resp
Пример #12
0
    def new(self, environ, request, uri, key):
        data = request.get_json()

        # check access keys
        rv = self.db.execute(['SELECT uri FROM access WHERE key = ? ;'],
                             (key, )).fetchall()
        if not rv or rv[0][0] != uri:
            raise Forbidden

        for field in set(data.keys()) - API.ACCEPT:
            data.pop(field)

        for key in ("author", "parent"):
            data.setdefault(key, None)

        if data['parent'] is not None:
            data.setdefault('place', None)

        valid, reason = API.verify(data)
        if not valid:
            return BadRequest(reason)

        escaped = dict((key, cgi.escape(value) if value is not None else None)
                       for key, value in data.items())

        added = {}
        if escaped.get("website") is not None:
            added["website"] = normalize(escaped["website"])

        added['mode'] = 2 if self.moderated else 1

        prepared = dict(escaped)
        prepared.update(added)

        with self.isso.lock:
            if uri in self._threads:
                thread = self._threads[uri]
            else:
                if 'title' in prepared:
                    title = prepared['title']
                else:
                    with http.curl('GET', local("origin"), uri) as resp:
                        if resp and resp.status == 200:
                            uri, title = parse.thread(resp.read(), id=uri)
                        else:
                            return NotFound('URI does not exist %s')

                thread = self._threads.new(uri, title)
                self.signal("comments.new:new-thread", thread)
        # notify extensions that the new comment is about to save
        self.signal("comments.new:before-save", thread, prepared)

        valid, reason = self.guard.validate(uri, prepared)
        if not valid:
            self.signal("comments.new:guard", reason)
            raise Forbidden(reason)

        with self.isso.lock:
            rv = self.comments.add(uri, prepared)

        # notify extension, that the new comment has been successfully saved
        self.signal("comments.new:after-save", thread, rv)

        cookie = functools.partial(dump_cookie,
                                   value=self.isso.sign(
                                       [rv["id"], sha1(rv["text"])]),
                                   max_age=self.conf.getint('max-age'))

        rv["text"] = self.isso.render(rv["text"])
        rv["hash"] = self.hash(rv['remote_addr'])

        self.cache.set('hash', (rv['remote_addr']).encode('utf-8'), rv['hash'])

        for key in set(rv.keys()) - API.FIELDS:
            rv.pop(key)

        # success!
        self.signal("comments.new:finish", thread, rv)

        resp = JSON(rv, 202 if rv["mode"] == 2 else 201)
        resp.headers.add("Set-Cookie", cookie(str(rv["id"])))
        resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"]))
        return resp
Пример #13
0
    def _sendmail(self, thread, comment, parent):
        # Build message.
        data = {
            "from": "Comments <*****@*****.**>",
            "subject": thread['title'],
            "text": "",
            "html": "",
            "o:tag": ["New Comment", "newcomment"]
        }

        if parent and parent['email'] != self.conf.monitor:
            data['to'] = parent['email']
            data['bcc'] = self.conf.monitor
        else:
            data['to'] = self.conf.monitor

        # Generate text and html version of the email.
        author = comment["author"] or "Anonymous"
        url = comment["website"] or ""
        comment_url = (local("origin") + thread["uri"] +
                       "#isso-%i" % comment["id"])
        text = '''
%s%s wrote:

%s

View the comment at: %s<%s>
'''.strip() % (author, ("<%s>" % url) if url else "", comment['text'],
               thread['title'], comment_url)

        # ...
        html = '''
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "EmailMessage",
  "potentialAction": {
    "@type": "ViewAction",
    "target": %s,
    "name": %s
  },
  "description": %s
}
</script>

<a href="%s" target="_blank">%s</a> have wrote:

<blockquote>%s</blockquote>

View the comment at: <a href="%s" target="_blank">%s</a>
'''.strip() % (json.dumps(comment_url), json.dumps("View Comment"),
               json.dumps('View new comment at "%s"' % thread['title']), url,
               author, self.isso.render(
                   comment['text']), comment_url, cgi.escape(thread['title']))
        # ...

        (data['text'], data['html']) = (text, html)
        requests.post('https://api.mailgun.net/v3/%s/messages' %
                      self.conf.domain,
                      auth=('api', self.conf.api_key),
                      data=data)
Пример #14
0
    def new(self, environ, request, uri):

        logger.debug("got uri :%s" % uri)
        logger.debug("got eviron :%s" % str(environ))
        logger.debug("got request :%s" % pprint.pformat(request.__dict__))
                
        data = request.get_json()

        logger.debug("got data :%s" % data)

        
        for field in set(data.keys()) - API.ACCEPT:
            f = data.pop(field)
            logger.debug("skip data %s:%s" % (field, f ))

        for key in ("author", "email", "website", "parent"):
            data.setdefault(key, None)

        valid, reason = API.verify(data)
        if not valid:
            return BadRequest(reason)

        for field in ("author", "email", "website"):
            if data.get(field) is not None:
                data[field] = cgi.escape(data[field])

        if data.get("website"):
            data["website"] = normalize(data["website"])

        data['mode'] = 2 if self.moderated else 1

        logger.debug("request.remote_addr %s" % (request.remote_addr ))
                    
        data['remote_addr'] = utils.anonymize(str(request.remote_addr))

        with self.isso.lock:
            if uri not in self.threads:
                org = str(local("origin"))
                if org == '<LocalProxy unbound>':
                    org = environ['HTTP_ORIGIN']

                logger.debug("got origin %s" % (org))
                
                with http.curl('GET', org, uri) as resp:
                    if resp and resp.status == 200:
                        uri, title = parse.thread(resp.read(), id=uri)
                    else:
                        return NotFound('URI does not exist')

                thread = self.threads.new(uri, title)
                self.signal("comments.new:new-thread", thread)
            else:
                thread = self.threads[uri]

        # notify extensions that the new comment is about to save
        self.signal("comments.new:before-save", thread, data)

        valid, reason = self.guard.validate(uri, data)
        if not valid:
            self.signal("comments.new:guard", reason)
            raise Forbidden(reason)

        with self.isso.lock:
            rv = self.comments.add(uri, data)

        # notify extension, that the new comment has been successfully saved
        self.signal("comments.new:after-save", thread, rv)

        cookie = functools.partial(dump_cookie,
            value=self.isso.sign([rv["id"], sha1(rv["text"])]),
            max_age=self.conf.getint('max-age'))

        rv["text"] = self.isso.render(rv["text"])
        rv["hash"] = self.hash(rv['email'] or rv['remote_addr'])

        self.cache.set('hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash'])

        for key in set(rv.keys()) - API.FIELDS:
            rv.pop(key)

        # success!
        self.signal("comments.new:finish", thread, rv)

        resp = JSON(rv, 202 if rv["mode"] == 2 else 201)
        resp.headers.add("Set-Cookie", cookie(str(rv["id"])))
        resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"]))
        return resp