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()
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")
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
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) """
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()
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()
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
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()
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
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
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
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)
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