Beispiel #1
0
    def token_request(self, session):
        # Token Request as per
        # http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
        params = {
            'grant_type': "authorization_code",
            'code': session['code'],
            'redirect_uri': session['redirect_uri'],
        }
        auth_string = "%s:%s" % (session['client_id'],
                                 session['client_secret'])
        headers = {
            'Content-Type':
            "application/x-www-form-urlencoded",
            'Authorization':
            "Basic %s" % (base64.b64encode(
                (auth_string.encode("ascii")).decode("ascii"))),
        }
        token_url = "%s/oauth/token" % (session['issuer'])
        try:
            with http.curl("POST",
                           token_url,
                           urlencode(params),
                           extra_headers=headers) as resp:
                assert resp and resp.getcode() == 200
                ans = json.loads(resp.read())
                session['access_token'] = ans['access_token']
        except (AssertionError, ValueError, KeyError):
            return False

        return True
Beispiel #2
0
def make_app(conf=None):

    try:
        import uwsgi
    except ImportError:

        class App(Isso, ThreadedMixin):
            pass
    else:

        class App(Isso, uWSGIMixin):
            pass

    isso = App(conf)

    for host in conf.getiter("general", "host"):
        with http.curl('HEAD', host, '/', 5) as resp:
            if resp is not None:
                logger.info("connected to HTTP server")
                break
    else:
        logger.warn("unable to connect to HTTP server")

    app = ProxyFix(
        wsgi.SubURI(
            SharedDataMiddleware(
                isso.wsgi_app, {
                    '/js': join(dirname(__file__), 'js/'),
                    '/css': join(dirname(__file__), 'css/')
                })))

    return app
Beispiel #3
0
def make_app(conf=None):

    try:
        import uwsgi
    except ImportError:
        class App(Isso, ThreadedMixin):
            pass
    else:
        class App(Isso, uWSGIMixin):
            pass

    isso = App(conf)

    for host in conf.getiter("general", "host"):
        with http.curl('HEAD', host, '/', 5) as resp:
            if resp is not None:
                logger.info("connected to HTTP server")
                break
    else:
        logger.warn("unable to connect to HTTP server")

    if isso.conf.getboolean("server", "profile"):
        from werkzeug.contrib.profiler import ProfilerMiddleware
        isso = ProfilerMiddleware(isso, sort_by=("cumtime", ), restrictions=("isso/(?!lib)", ))

    app = ProxyFix(
            wsgi.SubURI(
                wsgi.CORSMiddleware(
                    SharedDataMiddleware(isso, {
                        '/js': join(dirname(__file__), 'js/'),
                        '/css': join(dirname(__file__), 'css/')}),
                    list(isso.conf.getiter("general", "host")))))

    return app
Beispiel #4
0
 def sendmsg(self, title, body):
     if self.moderated:
         TA_view = body["v"]
         TA_delete = body["d"]
         TA_activate = body["a"]
         body = body["rv"]
     if PY2K:
         if title:
             title = title.encode("utf-8")
         body = body.encode("utf-8")
     if self.moderated:
         # Need to review comments, use the TalkAdmin interface.
         host = "http://sc.ftqq.com"
         path = "/webhook/%s?%s" % (self.conf.get("takey"), urlencode(dict(
             TA_action_on=1,
             TA_title=title,
             TA_content=body,
             TA_view=TA_view,
             TA_delete=TA_delete,
             TA_activate=TA_activate
         )))
     else:
         host = "https://sc.ftqq.com"
         path = "/%s.send?%s" % (self.conf.get("sckey"), urlencode(dict(
             text=title, desp=body
         )))
     with curl("POST", host, path, 5) as resp:
         if resp:
             try:
                 result = json.loads(resp.read())
             except (TypeError, ValueError):
                 logger.error("Illegal response structure, request error.")
             else:
                 if result.get("errno") != 0:
                     logger.warn(result)
Beispiel #5
0
def make_app(conf=None):

    try:
        import uwsgi
    except ImportError:
        class App(Isso, ThreadedMixin):
            pass
    else:
        class App(Isso, uWSGIMixin):
            pass

    isso = App(conf)

    for host in conf.getiter("general", "host"):
        with http.curl('HEAD', host, '/', 5) as resp:
            if resp is not None:
                logger.info("connected to HTTP server")
                break
    else:
        logger.warn("unable to connect to HTTP server")

    app = ProxyFix(wsgi.SubURI(SharedDataMiddleware(isso.wsgi_app, {
        '/js': join(dirname(__file__), 'js/'),
        '/css': join(dirname(__file__), 'css/')
        })))

    return app
Beispiel #6
0
def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False):

    if not any((threading, multiprocessing, uwsgi)):
        raise RuntimeError("either set threading, multiprocessing or uwsgi")

    if threading:

        class App(Isso, ThreadedMixin):
            pass
    elif multiprocessing:

        class App(Isso, ProcessMixin):
            pass
    else:

        class App(Isso, uWSGIMixin):
            pass

    isso = App(conf)

    # check HTTP server connection
    for host in conf.getiter("general", "host"):
        with http.curl('HEAD', host, '/', 5) as resp:
            if resp is not None:
                logger.info("connected to %s", host)
                break
    else:
        logger.warn(
            "unable to connect to your website, Isso will probably not "
            "work correctly. Please make sure, Isso can reach your "
            "website via HTTP(S).")

    wrapper = [local_manager.make_middleware]

    if isso.conf.getboolean("server", "profile"):
        wrapper.append(
            partial(ProfilerMiddleware,
                    sort_by=("cumulative", ),
                    restrictions=("isso/(?!lib)", 10)))

    wrapper.append(
        partial(SharedDataMiddleware,
                exports={
                    '/js': join(dirname(__file__), 'js/'),
                    '/css': join(dirname(__file__), 'css/'),
                    '/demo': join(dirname(__file__), 'demo/')
                }))

    wrapper.append(
        partial(wsgi.CORSMiddleware,
                origin=origin(isso.conf.getiter("general", "host")),
                allowed=("Origin", "Referer", "Content-Type"),
                exposed=("X-Set-Cookie", "Date")))

    wrapper.extend([wsgi.SubURI, ProxyFix])

    if werkzeug.version.startswith("0.8"):
        wrapper.append(wsgi.LegacyWerkzeugMiddleware)

    return reduce(lambda x, f: f(x), wrapper, isso)
Beispiel #7
0
    def discovery(self, session):
        # Provider Discovery as per
        # http://openid.net/specs/openid-connect-discovery-1_0.html

        id_normed, wf_host = self.id_normalize(session['identifier'])
        params = {
            'resource': id_normed,
            'rel': "http://openid.net/specs/connect/1.0/issuer",
        }
        try:
            assert wf_host
            with http.curl(
                    "GET", "https://%s/.well-known/webfinger?%s" %
                (wf_host, urlencode(params))) as resp:
                assert resp and resp.getcode() == 200
                ans = json.loads(resp.read())
                for link in ans['links']:
                    if link['rel'] == "http://openid.net/specs/connect/1.0/issuer":
                        session['issuer'] = link['href']
                        u = urlsplit(session['issuer'])
                        assert u.scheme == "https"
                        assert u.hostname
                        assert not u.query and not u.fragment
                        break
                assert 'issuer' in session
        except (AssertionError, ValueError, KeyError):
            return False

        try:
            with http.curl(
                    "GET", session['issuer'].rstrip("/") +
                    "/.well-known/openid-configuration") as resp:
                assert resp and resp.getcode() == 200
                ans = json.loads(resp.read())
                assert ans['issuer'] == session['issuer']
                session['registration_endpoint'] = ans['registration_endpoint']
                session['userinfo_endpoint'] = ans['userinfo_endpoint']
        except (AssertionError, ValueError, KeyError):
            return False

        return True
Beispiel #8
0
def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False):

    if not any((threading, multiprocessing, uwsgi)):
        raise RuntimeError("either set threading, multiprocessing or uwsgi")

    if threading:
        class App(Isso, ThreadedMixin):
            pass
    elif multiprocessing:
        class App(Isso, ProcessMixin):
            pass
    else:
        class App(Isso, uWSGIMixin):
            pass

    isso = App(conf)



    # check HTTP server connection
    for host in conf.getiter("general", "host"):
        with http.curl('HEAD', host, '/', 5) as resp:
            if resp is not None:
                logger.info("connected to %s", host)
                break
    else:
        logger.warn("unable to connect to your website, Isso will probably not "
                    "work correctly. Please make sure, Isso can reach your "
                    "website via HTTP(S).")

    wrapper = [local_manager.make_middleware]

    if isso.conf.getboolean("server", "profile"):
        wrapper.append(partial(ProfilerMiddleware,
            sort_by=("cumulative", ), restrictions=("isso/(?!lib)", 10)))

    wrapper.append(partial(SharedDataMiddleware, exports={
        '/js': join(dirname(__file__), 'js/'),
        '/css': join(dirname(__file__), 'css/'),
        '/demo': join(dirname(__file__), 'demo/')
        }))

    wrapper.append(partial(wsgi.CORSMiddleware,
        origin=origin(isso.conf.getiter("general", "host")),
        allowed=("Origin", "Referer", "Content-Type"),
        exposed=("X-Set-Cookie", "Date")))

    wrapper.extend([wsgi.SubURI, ProxyFix, wsgi.LegacyWerkzeugMiddleware])

    if werkzeug.version.startswith("0.8"):
        wrapper.append(wsgi.LegacyWerkzeugMiddleware)

    return reduce(lambda x, f: f(x), wrapper, isso)
Beispiel #9
0
 def update_google_certs(cls):
     if API.google_certs:
         for key in API.google_certs:
             if datetime.utcnow() <= API.google_certs[key].not_valid_after:
                 return
     with http.curl('GET', API.GOOGLE_CERT_URL, timeout=5) as resp:
         try:
             assert resp and resp.getcode() == 200
             pems = json.loads(resp.read())
             API.google_certs = {key: load_pem_x509_certificate(pems[key].encode("ascii"), default_backend()) for key in pems}
         except (AssertionError, ValueError):
             logger.warn("failed to renew Google certificates")
Beispiel #10
0
 def validate_fb_token(self, token, uid):
     appid = self.facebook_conf.get("app-id")
     req_url = "%s/debug_token?input_token=%s&access_token=%s|%s" % (API.FACEBOOK_GRAPH_HOST, token, appid,
                                                                     self.facebook_conf.get("app-secret"))
     with http.curl('GET', req_url, timeout=5) as resp:
         try:
             assert resp and resp.getcode() == 200
             data = json.loads(resp.read())["data"]
             assert data["is_valid"]
             assert data["user_id"] == uid
             assert data["app_id"] == appid
             assert datetime.utcnow() <= datetime.utcfromtimestamp(data["expires_at"])
             return True
         except (AssertionError, ValueError, KeyError):
             return False
Beispiel #11
0
    def userinfo_request(self, session):
        # UserInfo Request as per
        # http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
        headers = {
            'Authorization': "Bearer %s" % (session['access_token']),
        }
        try:
            with http.curl("GET",
                           session['userinfo_endpoint'],
                           extra_headers=headers) as resp:
                assert resp and resp.getcode() == 200
                session['userinfo'] = json.loads(resp.read())
        except (AssertionError, ValueError):
            return False

        return True
Beispiel #12
0
 def update_google_certs(cls):
     if API.google_certs:
         for key in API.google_certs:
             if datetime.utcnow() <= API.google_certs[key].not_valid_after:
                 return
     with http.curl('GET', API.GOOGLE_CERT_URL, timeout=5) as resp:
         try:
             assert resp and resp.getcode() == 200
             pems = json.loads(resp.read())
             API.google_certs = {
                 key: load_pem_x509_certificate(pems[key].encode("ascii"),
                                                default_backend())
                 for key in pems
             }
         except (AssertionError, ValueError):
             logger.warn("failed to renew Google certificates")
Beispiel #13
0
def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False):

    if not any((threading, multiprocessing, uwsgi)):
        raise RuntimeError("either set threading, multiprocessing or uwsgi")

    if threading:
        class App(Isso, ThreadedMixin):
            pass
    elif multiprocessing:
        class App(Isso, ProcessMixin):
            pass
    else:
        class App(Isso, uWSGIMixin):
            pass

    isso = App(conf)

    # show session-key (to see that it changes randomely if unset)
    logger.info("session-key = %s", isso.conf.get("general", "session-key"))

    # check HTTP server connection
    for host in conf.getiter("general", "host"):
        with http.curl('HEAD', host, '/', 5) as resp:
            if resp is not None:
                logger.info("connected to %s", host)
                break
    else:
        logger.warn("unable to connect to %s", ", ".join(conf.getiter("general", "host")))

    wrapper = [local_manager.make_middleware]

    if isso.conf.getboolean("server", "profile"):
        wrapper.append(partial(ProfilerMiddleware,
            sort_by=("cumulative", ), restrictions=("isso/(?!lib)", 10)))

    wrapper.append(partial(SharedDataMiddleware, exports={
        '/js': join(dirname(__file__), 'js/'),
        '/css': join(dirname(__file__), 'css/')}))

    wrapper.append(partial(wsgi.CORSMiddleware,
        origin=origin(isso.conf.getiter("general", "host")),
        allowed=("Origin", "Content-Type"), exposed=("X-Set-Cookie", "Date")))

    wrapper.extend([wsgi.SubURI, ProxyFix])

    return reduce(lambda x, f: f(x), wrapper, isso)
Beispiel #14
0
 def validate_fb_token(self, token, uid):
     appid = self.facebook_conf.get("app-id")
     req_url = "%s/debug_token?input_token=%s&access_token=%s|%s" % (
         API.FACEBOOK_GRAPH_HOST, token, appid,
         self.facebook_conf.get("app-secret"))
     with http.curl('GET', req_url, timeout=5) as resp:
         try:
             assert resp and resp.getcode() == 200
             data = json.loads(resp.read())["data"]
             assert data["is_valid"]
             assert data["user_id"] == uid
             assert data["app_id"] == appid
             assert datetime.utcnow() <= datetime.utcfromtimestamp(
                 data["expires_at"])
             return True
         except (AssertionError, ValueError, KeyError):
             return False
Beispiel #15
0
def make_app(conf=None):

    try:
        import uwsgi
    except ImportError:

        class App(Isso, ThreadedMixin):
            pass
    else:

        class App(Isso, uWSGIMixin):
            pass

    isso = App(conf)

    for host in conf.getiter("general", "host"):
        with http.curl('HEAD', host, '/', 5) as resp:
            if resp is not None:
                logger.info("connected to HTTP server")
                break
    else:
        logger.warn("unable to connect to HTTP server")

    if isso.conf.getboolean("server", "profile"):
        from werkzeug.contrib.profiler import ProfilerMiddleware
        isso = ProfilerMiddleware(isso,
                                  sort_by=("cumtime", ),
                                  restrictions=("isso/(?!lib)", ))

    app = ProxyFix(
        wsgi.SubURI(
            wsgi.CORSMiddleware(
                SharedDataMiddleware(
                    isso, {
                        '/js': join(dirname(__file__), 'js/'),
                        '/css': join(dirname(__file__), 'css/')
                    }), list(isso.conf.getiter("general", "host")))))

    return app
Beispiel #16
0
def make_app(conf=None):

    if uwsgi:
        class App(Isso, uWSGIMixin):
            pass
    elif gevent or sys.argv[0].endswith("isso"):
        class App(Isso, ThreadedMixin):
            pass
    else:
        class App(Isso, ProcessMixin):
            pass

    isso = App(conf)

    for host in conf.getiter("general", "host"):
        with http.curl('HEAD', host, '/', 5) as resp:
            if resp is not None:
                logger.info("connected to %s", host)
                break
    else:
        logger.warn("unable to connect to %s", ", ".join(conf.getiter("general", "host")))

    wrapper = [local_manager.make_middleware]

    if isso.conf.getboolean("server", "profile"):
        wrapper.append(partial(ProfilerMiddleware,
            sort_by=("cumtime", ), restrictions=("isso/(?!lib)", 10)))

    wrapper.append(partial(SharedDataMiddleware, exports={
        '/js': join(dirname(__file__), 'js/'),
        '/css': join(dirname(__file__), 'css/')}))

    wrapper.append(partial(wsgi.CORSMiddleware,
        origin=origin(isso.conf.getiter("general", "host"))))

    wrapper.extend([wsgi.SubURI, ProxyFix])

    return reduce(lambda x, f: f(x), wrapper, isso)
Beispiel #17
0
    def dyn_register(self, session):
        #  Dynamic Client Registration as per
        #  http://openid.net/specs/openid-connect-registration-1_0.html
        reg_data = {
            'client_name': "Isso",
            'redirect_uris': [session['redirect_uri']],
        }
        headers = {
            'Content-Type': "application/json",
        }
        try:
            with http.curl("POST",
                           session['registration_endpoint'],
                           body=json.dumps(reg_data),
                           extra_headers=headers) as resp:
                # HTTP status should be 201 on success, but is returned as 200 from some implementations
                assert resp and 200 <= resp.getcode() < 300
                ans = json.loads(resp.read())
                session['client_id'] = ans['client_id']
                session['client_secret'] = ans['client_secret']
        except (AssertionError, ValueError, KeyError):
            return False

        return True
Beispiel #18
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
Beispiel #19
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
Beispiel #20
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
Beispiel #21
0
def new(app, environ, request, uri):

    data = request.get_json()

    for field in set(data.keys()) - set(['text', 'author', 'website', 'email', 'parent']):
        data.pop(field)

    if "text" not in data or data["text"] is None or len(data["text"]) < 3:
        raise BadRequest("no text given")

    if "id" in data and not isinstance(data["id"], int):
        raise BadRequest("parent id must be an integer")

    if len(data.get("email") or "") > 254:
        raise BadRequest("http://tools.ietf.org/html/rfc5321#section-4.5.3")

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

    data['mode'] = (app.conf.getboolean('moderation', 'enabled') and 2) or 1
    data['remote_addr'] = utils.anonymize(str(request.remote_addr))

    with app.lock:
        if uri not in app.db.threads:
            for host in app.conf.getiter('general', 'host'):
                with http.curl('GET', host, uri) as resp:
                    if resp and resp.status == 200:
                        title = parse.title(resp.read())
                        break
            else:
                return Response('URI does not exist', 404)

            app.db.threads.new(uri, title)
            logger.info('new thread: %s -> %s', uri, title)
        else:
            title = app.db.threads[uri].title

    try:
        with app.lock:
            rv = app.db.comments.add(uri, data)
    except db.IssoDBException:
        raise Forbidden

    host = list(app.conf.getiter('general', 'host'))[0].rstrip("/")
    href = host + uri + "#isso-%i" % rv["id"]

    deletion = host + environ["SCRIPT_NAME"] + "/delete/" + app.sign(str(rv["id"]))
    activation = None

    if app.conf.getboolean('moderation', 'enabled'):
        activation = host + environ["SCRIPT_NAME"] + "/activate/" + app.sign(str(rv["id"]))

    app.notify(title, notify.format(rv, href, utils.anonymize(str(request.remote_addr)),
                                    activation_key=activation, deletion_key=deletion))

    # save checksum of text into cookie, so mallory can't modify/delete a comment, if
    # he add a comment, then removed it but not the signed cookie.
    checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest()

    rv["text"] = app.markdown(rv["text"])
    rv["hash"] = str(pbkdf2(rv['email'] or rv['remote_addr'], app.salt, 1000, 6))

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

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

    # success!
    logger.info('comment created: %s', json.dumps(rv))

    cookie = functools.partial(dump_cookie,
        value=app.sign([rv["id"], checksum]),
        max_age=app.conf.getint('general', 'max-age'))

    resp = Response(json.dumps(rv), 202 if rv["mode"] == 2 else 201, content_type='application/json')
    resp.headers.add("Set-Cookie", cookie(str(rv["id"])))
    resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"]))
    return resp
Beispiel #22
0
def new(app, environ, request, uri):

    data = request.get_json()

    for field in set(data.keys()) - set(
        ['text', 'author', 'website', 'email', 'parent']):
        data.pop(field)

    if not data.get("text"):
        raise BadRequest("no text given")

    if "id" in data and not isinstance(data["id"], int):
        raise BadRequest("parent id must be an integer")

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

    data['mode'] = (app.conf.getboolean('moderation', 'enabled') and 2) or 1
    data['remote_addr'] = utils.anonymize(str(request.remote_addr))

    with app.lock:
        if uri not in app.db.threads:
            for host in app.conf.getiter('general', 'host'):
                with http.curl('GET', host, uri) as resp:
                    if resp and resp.status == 200:
                        title = parse.title(resp.read())
                        break
            else:
                return Response('URI does not exist', 404)

            app.db.threads.new(uri, title)
            logger.info('new thread: %s -> %s', uri, title)
        else:
            title = app.db.threads[uri].title

    try:
        with app.lock:
            rv = app.db.comments.add(uri, data)
    except db.IssoDBException:
        raise Forbidden

    host = list(app.conf.getiter('general', 'host'))[0].rstrip("/")
    href = host + uri + "#isso-%i" % rv["id"]

    deletion = host + environ["SCRIPT_NAME"] + "/delete/" + app.sign(
        str(rv["id"]))
    activation = None

    if app.conf.getboolean('moderation', 'enabled'):
        activation = host + environ["SCRIPT_NAME"] + "/activate/" + app.sign(
            str(rv["id"]))

    app.notify(
        title,
        notify.format(rv,
                      href,
                      utils.anonymize(str(request.remote_addr)),
                      activation_key=activation,
                      deletion_key=deletion))

    # save checksum of text into cookie, so mallory can't modify/delete a comment, if
    # he add a comment, then removed it but not the signed cookie.
    checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest()

    rv["text"] = app.markdown(rv["text"])
    rv["hash"] = str(
        pbkdf2(rv.get('email') or rv['remote_addr'], app.salt, 1000, 6))

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

    # success!
    logger.info('comment created: %s', json.dumps(rv))

    resp = Response(json.dumps(rv),
                    202 if rv["mode"] == 2 else 201,
                    content_type='application/json')
    resp.set_cookie(str(rv["id"]),
                    app.sign([rv["id"], checksum]),
                    max_age=app.conf.getint('general', 'max-age'))
    return resp
Beispiel #23
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