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 =
|
name =
|
||||||
host = http://localhost:8080/
|
host = http://localhost:8080/
|
||||||
max-age = 15m
|
max-age = 15m
|
||||||
session-key = ... ; python: binascii.b2a_hex(os.urandom(24))
|
|
||||||
notify =
|
notify =
|
||||||
|
|
||||||
dbpath
|
dbpath
|
||||||
@ -71,11 +70,6 @@ host
|
|||||||
|
|
||||||
This is useful, when your website is available on HTTP and HTTPS.
|
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
|
max-age
|
||||||
time range that allows users to edit/remove their own comments. See
|
time range that allows users to edit/remove their own comments. See
|
||||||
:ref:`Appendum: Timedelta <appendum-timedelta>` for valid values.
|
:ref:`Appendum: Timedelta <appendum-timedelta>` for valid values.
|
||||||
@ -136,8 +130,7 @@ listen
|
|||||||
|
|
||||||
reload
|
reload
|
||||||
reload application, when the source code has changed. Useful for
|
reload application, when the source code has changed. Useful for
|
||||||
development (don't forget to use a fixed `session-key`). Only works
|
development. Only works with the internal webserver.
|
||||||
when ``gevent`` and ``uwsgi`` are *not* available.
|
|
||||||
|
|
||||||
profile
|
profile
|
||||||
show 10 most time consuming function in Isso after each request. Do
|
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"))
|
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>`__
|
`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"))
|
application = make_app(Config.load("/path/to/isso.cfg"))
|
||||||
WSGIServer(application).run()
|
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.
|
# 3h45m12s equals to 3 hours, 45 minutes and 12 seconds.
|
||||||
max-age = 15m
|
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
|
# Select notification backend for new comments. Currently, only SMTP is
|
||||||
# available.
|
# available.
|
||||||
notify =
|
notify =
|
||||||
@ -53,9 +49,8 @@ purge-after = 30d
|
|||||||
# for details). Does not apply for uWSGI.
|
# for details). Does not apply for uWSGI.
|
||||||
listen = http://localhost:8080
|
listen = http://localhost:8080
|
||||||
|
|
||||||
# reload application, when the source code has changed. Useful for development
|
# 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
|
# Only works with the internal webserver.
|
||||||
# are not available.
|
|
||||||
reload = off
|
reload = off
|
||||||
|
|
||||||
# show 10 most time consuming function in Isso after each request. Do not use in
|
# 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.conf = conf
|
||||||
self.db = db.SQLite3(conf.get('general', 'dbpath'), 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'))
|
self.markup = html.Markup(conf.section('markup'))
|
||||||
|
|
||||||
super(Isso, self).__init__(conf)
|
super(Isso, self).__init__(conf)
|
||||||
@ -160,9 +160,6 @@ def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False):
|
|||||||
|
|
||||||
isso = App(conf)
|
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
|
# check HTTP server connection
|
||||||
for host in conf.getiter("general", "host"):
|
for host in conf.getiter("general", "host"):
|
||||||
with http.curl('HEAD', host, '/', 5) as resp:
|
with http.curl('HEAD', host, '/', 5) as resp:
|
||||||
|
@ -3,10 +3,8 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import binascii
|
|
||||||
import threading
|
import threading
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
|
||||||
@ -115,7 +113,7 @@ class Config:
|
|||||||
default = [
|
default = [
|
||||||
"[general]",
|
"[general]",
|
||||||
"name = ",
|
"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",
|
"host = http://localhost:8080/", "max-age = 15m",
|
||||||
"notify = ",
|
"notify = ",
|
||||||
"[moderation]",
|
"[moderation]",
|
||||||
@ -164,6 +162,9 @@ class Config:
|
|||||||
logger.warn("use `listen = http://$host:$port` instead")
|
logger.warn("use `listen = http://$host:$port` instead")
|
||||||
if item == ("smtp", "ssl"):
|
if item == ("smtp", "ssl"):
|
||||||
logger.warn("use `security = none | starttls | ssl` instead")
|
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"):
|
if rv.get("smtp", "username") and not rv.get("general", "notify"):
|
||||||
logger.warn(("SMTP is no longer enabled by default, add "
|
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.comments import Comments
|
||||||
from isso.db.threads import Threads
|
from isso.db.threads import Threads
|
||||||
from isso.db.spam import Guard
|
from isso.db.spam import Guard
|
||||||
|
from isso.db.preferences import Preferences
|
||||||
|
|
||||||
|
|
||||||
class SQLite3:
|
class SQLite3:
|
||||||
@ -18,7 +19,7 @@ class SQLite3:
|
|||||||
a trigger for automated orphan removal.
|
a trigger for automated orphan removal.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MAX_VERSION = 1
|
MAX_VERSION = 2
|
||||||
|
|
||||||
def __init__(self, path, conf):
|
def __init__(self, path, conf):
|
||||||
|
|
||||||
@ -27,18 +28,19 @@ class SQLite3:
|
|||||||
|
|
||||||
rv = self.execute([
|
rv = self.execute([
|
||||||
"SELECT name FROM sqlite_master"
|
"SELECT name FROM sqlite_master"
|
||||||
" WHERE type='table' AND name IN ('threads', 'comments')"]
|
" WHERE type='table' AND name IN ('threads', 'comments', 'preferences')"]
|
||||||
).fetchall()
|
).fetchone()
|
||||||
|
|
||||||
if rv:
|
|
||||||
self.migrate(to=SQLite3.MAX_VERSION)
|
|
||||||
else:
|
|
||||||
self.execute("PRAGMA user_version = %i" % SQLite3.MAX_VERSION)
|
|
||||||
|
|
||||||
|
self.preferences = Preferences(self)
|
||||||
self.threads = Threads(self)
|
self.threads = Threads(self)
|
||||||
self.comments = Comments(self)
|
self.comments = Comments(self)
|
||||||
self.guard = Guard(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([
|
self.execute([
|
||||||
'CREATE TRIGGER IF NOT EXISTS remove_stale_threads',
|
'CREATE TRIGGER IF NOT EXISTS remove_stale_threads',
|
||||||
'AFTER DELETE ON comments',
|
'AFTER DELETE ON comments',
|
||||||
@ -76,3 +78,14 @@ class SQLite3:
|
|||||||
con.execute('UPDATE comments SET voters=?', (bf, ))
|
con.execute('UPDATE comments SET voters=?', (bf, ))
|
||||||
con.execute('PRAGMA user_version = 1')
|
con.execute('PRAGMA user_version = 1')
|
||||||
logger.info("%i rows changed", con.total_changes)
|
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