store session-key in database (once generated on db creation), #74
Store a random session key used to sign and verify comment ownership once the database is initialized, not on every application startup. Currently fixed session keys in [general] session-key are migrated into the database on startup. The configuration parser will notice you about the change and suggest you to remove this option.
This commit is contained in:
parent
8f293ad435
commit
0b816a0677
@ -43,7 +43,6 @@ session key and hostname. Here are the default values for this section:
|
||||
name =
|
||||
host = http://localhost:8080/
|
||||
max-age = 15m
|
||||
session-key = ... ; python: binascii.b2a_hex(os.urandom(24))
|
||||
notify =
|
||||
|
||||
dbpath
|
||||
@ -71,11 +70,6 @@ host
|
||||
|
||||
This is useful, when your website is available on HTTP and HTTPS.
|
||||
|
||||
session-key
|
||||
private session key to validate client cookies. If you restart the
|
||||
application several times per hour for whatever reason, use a fixed
|
||||
key.
|
||||
|
||||
max-age
|
||||
time range that allows users to edit/remove their own comments. See
|
||||
:ref:`Appendum: Timedelta <appendum-timedelta>` for valid values.
|
||||
@ -136,8 +130,7 @@ listen
|
||||
|
||||
reload
|
||||
reload application, when the source code has changed. Useful for
|
||||
development (don't forget to use a fixed `session-key`). Only works
|
||||
when ``gevent`` and ``uwsgi`` are *not* available.
|
||||
development. Only works with the internal webserver.
|
||||
|
||||
profile
|
||||
show 10 most time consuming function in Isso after each request. Do
|
||||
|
@ -121,15 +121,6 @@ Next, copy'n'paste to `/var/www/isso.wsgi`:
|
||||
|
||||
application = make_app(Config.load("/path/to/isso.cfg"))
|
||||
|
||||
Also make sure, you set a static key because `mod_wsgi` generates a session
|
||||
key per thread/process. This may result in random 403 errors when you edit or
|
||||
delete comments.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[general]
|
||||
; cat /dev/urandom | strings | grep -o '[[:alnum:]]' | head -n 30 | tr -d '\n'
|
||||
session-key = superrandomkey1
|
||||
|
||||
`mod_fastcgi <http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -170,12 +161,3 @@ Next, copy'n'paste to `/var/www/isso.fcgi` (or whatever location you prefer):
|
||||
|
||||
application = make_app(Config.load("/path/to/isso.cfg"))
|
||||
WSGIServer(application).run()
|
||||
|
||||
Similar to mod_wsgi_, set a static session key if you are using more than one process
|
||||
to avoid random errors.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[general]
|
||||
; cat /dev/urandom | strings | grep -o '[[:alnum:]]' | head -n 30 | tr -d '\n'
|
||||
session-key = superrandomkey1
|
||||
|
@ -24,10 +24,6 @@ host = http://localhost/
|
||||
# 3h45m12s equals to 3 hours, 45 minutes and 12 seconds.
|
||||
max-age = 15m
|
||||
|
||||
# private session key to validate client cookies. If you restart the application
|
||||
# several times per hour for whatever reason, use a fixed key.
|
||||
session-key = ... ; python: binascii.b2a_hex(os.urandom(24))
|
||||
|
||||
# Select notification backend for new comments. Currently, only SMTP is
|
||||
# available.
|
||||
notify =
|
||||
@ -53,9 +49,8 @@ purge-after = 30d
|
||||
# for details). Does not apply for uWSGI.
|
||||
listen = http://localhost:8080
|
||||
|
||||
# reload application, when the source code has changed. Useful for development
|
||||
# (don't forget to use a fixed session-key). Only works when gevent and uwsgi
|
||||
# are not available.
|
||||
# reload application, when the source code has changed. Useful for development.
|
||||
# Only works with the internal webserver.
|
||||
reload = off
|
||||
|
||||
# show 10 most time consuming function in Isso after each request. Do not use in
|
||||
|
@ -85,7 +85,7 @@ class Isso(object):
|
||||
|
||||
self.conf = conf
|
||||
self.db = db.SQLite3(conf.get('general', 'dbpath'), conf)
|
||||
self.signer = URLSafeTimedSerializer(conf.get('general', 'session-key'))
|
||||
self.signer = URLSafeTimedSerializer(self.db.preferences.get("session-key"))
|
||||
self.markup = html.Markup(conf.section('markup'))
|
||||
|
||||
super(Isso, self).__init__(conf)
|
||||
@ -160,9 +160,6 @@ def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False):
|
||||
|
||||
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:
|
||||
|
@ -3,10 +3,8 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import binascii
|
||||
import threading
|
||||
import multiprocessing
|
||||
|
||||
@ -115,7 +113,7 @@ class Config:
|
||||
default = [
|
||||
"[general]",
|
||||
"name = ",
|
||||
"dbpath = /tmp/isso.db", "session-key = %s" % binascii.b2a_hex(os.urandom(16)),
|
||||
"dbpath = /tmp/isso.db",
|
||||
"host = http://localhost:8080/", "max-age = 15m",
|
||||
"notify = ",
|
||||
"[moderation]",
|
||||
@ -164,6 +162,9 @@ class Config:
|
||||
logger.warn("use `listen = http://$host:$port` instead")
|
||||
if item == ("smtp", "ssl"):
|
||||
logger.warn("use `security = none | starttls | ssl` instead")
|
||||
if item == ("general", "session-key"):
|
||||
logger.info("Your `session-key` has been stored in the "
|
||||
"database itself, this option is now unused")
|
||||
|
||||
if rv.get("smtp", "username") and not rv.get("general", "notify"):
|
||||
logger.warn(("SMTP is no longer enabled by default, add "
|
||||
|
@ -9,6 +9,7 @@ logger = logging.getLogger("isso")
|
||||
from isso.db.comments import Comments
|
||||
from isso.db.threads import Threads
|
||||
from isso.db.spam import Guard
|
||||
from isso.db.preferences import Preferences
|
||||
|
||||
|
||||
class SQLite3:
|
||||
@ -18,7 +19,7 @@ class SQLite3:
|
||||
a trigger for automated orphan removal.
|
||||
"""
|
||||
|
||||
MAX_VERSION = 1
|
||||
MAX_VERSION = 2
|
||||
|
||||
def __init__(self, path, conf):
|
||||
|
||||
@ -27,18 +28,19 @@ class SQLite3:
|
||||
|
||||
rv = self.execute([
|
||||
"SELECT name FROM sqlite_master"
|
||||
" WHERE type='table' AND name IN ('threads', 'comments')"]
|
||||
).fetchall()
|
||||
|
||||
if rv:
|
||||
self.migrate(to=SQLite3.MAX_VERSION)
|
||||
else:
|
||||
self.execute("PRAGMA user_version = %i" % SQLite3.MAX_VERSION)
|
||||
" WHERE type='table' AND name IN ('threads', 'comments', 'preferences')"]
|
||||
).fetchone()
|
||||
|
||||
self.preferences = Preferences(self)
|
||||
self.threads = Threads(self)
|
||||
self.comments = Comments(self)
|
||||
self.guard = Guard(self)
|
||||
|
||||
if rv is None:
|
||||
self.execute("PRAGMA user_version = %i" % SQLite3.MAX_VERSION)
|
||||
else:
|
||||
self.migrate(to=SQLite3.MAX_VERSION)
|
||||
|
||||
self.execute([
|
||||
'CREATE TRIGGER IF NOT EXISTS remove_stale_threads',
|
||||
'AFTER DELETE ON comments',
|
||||
@ -76,3 +78,14 @@ class SQLite3:
|
||||
con.execute('UPDATE comments SET voters=?', (bf, ))
|
||||
con.execute('PRAGMA user_version = 1')
|
||||
logger.info("%i rows changed", con.total_changes)
|
||||
|
||||
# move [general] session-key to database
|
||||
if self.version == 1:
|
||||
|
||||
with sqlite3.connect(self.path) as con:
|
||||
if self.conf.has_option("general", "session-key"):
|
||||
con.execute('UPDATE preferences SET value=? WHERE key=?', (
|
||||
self.conf.get("general", "session-key"), "session-key"))
|
||||
|
||||
con.execute('PRAGMA user_version = 2')
|
||||
logger.info("%i rows changed", con.total_changes)
|
||||
|
36
isso/db/preferences.py
Normal file
36
isso/db/preferences.py
Normal file
@ -0,0 +1,36 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import binascii
|
||||
|
||||
|
||||
class Preferences:
|
||||
|
||||
defaults = [
|
||||
("session-key", binascii.b2a_hex(os.urandom(24))),
|
||||
]
|
||||
|
||||
def __init__(self, db):
|
||||
|
||||
self.db = db
|
||||
self.db.execute([
|
||||
'CREATE TABLE IF NOT EXISTS preferences (',
|
||||
' key VARCHAR PRIMARY KEY, value VARCHAR',
|
||||
');'])
|
||||
|
||||
for (key, value) in Preferences.defaults:
|
||||
if self.get(key) is None:
|
||||
self.set(key, value)
|
||||
|
||||
def get(self, key, default=None):
|
||||
rv = self.db.execute(
|
||||
'SELECT value FROM preferences WHERE key=?', (key, )).fetchone()
|
||||
|
||||
if rv is None:
|
||||
return default
|
||||
|
||||
return rv[0]
|
||||
|
||||
def set(self, key, value):
|
||||
self.db.execute(
|
||||
'INSERT INTO preferences (key, value) VALUES (?, ?)', (key, value))
|
Loading…
Reference in New Issue
Block a user