Add basic user management to the server

This code is mostly just a stub, will need to be fleshed out later.
This commit is contained in:
Kovid Goyal 2015-11-04 19:53:01 +05:30
parent 08fa7a72c1
commit 424a430d15
6 changed files with 87 additions and 10 deletions

View File

@ -6,7 +6,7 @@
<meta name="robots" content="noindex"> <meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="favicon.png"> <link rel="icon" type="image/png" href="favicon.png">
<script>window.calibre_entry_point = 'ENTRY_POINT'; window.calibre_username = USERNAME;</script> <script>window.calibre_entry_point = 'ENTRY_POINT';</script>
<script type="text/javascript" src="static/main.js"></script> <script type="text/javascript" src="static/main.js"></script>
<link rel="stylesheet" href="static/reset.css"></link> <link rel="stylesheet" href="static/reset.css"></link>
<link rel="stylesheet" href="static/font-awesome/fa.css"></link> <link rel="stylesheet" href="static/font-awesome/fa.css"></link>

View File

@ -593,7 +593,19 @@ def interface_data(ctx, rd):
''' '''
ans = {'username':rd.username} ans = {'username':rd.username}
ans['library_map'], ans['default_library'] = ctx.library_map ans['library_map'], ans['default_library'] = ctx.library_map
ud = {}
if rd.username:
# Override session data with stored values for the authenticated user,
# if any
ud = ctx.user_manager.get_session_data(rd.username)
lid = ud.get('library_id')
if lid and lid in ans['library_map']:
rd.query.set('library_id', lid)
usort = ud.get('sort')
if usort:
rd.query.set('sort', usort)
ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd.query) ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd.query)
ans['user_session_data'] = ud
try: try:
num = int(rd.query.get('num', 50)) num = int(rd.query.get('num', 50))
except Exception: except Exception:

View File

@ -4,7 +4,7 @@
from __future__ import (unicode_literals, division, absolute_import, from __future__ import (unicode_literals, division, absolute_import,
print_function) print_function)
import re, json import re
from functools import partial from functools import partial
from threading import Lock from threading import Lock
@ -44,5 +44,4 @@ def get_html(name, auto_reload_port, **replacements):
def index(ctx, rd): def index(ctx, rd):
return rd.generate_static_output('/', partial( return rd.generate_static_output('/', partial(
get_html, 'content-server/index.html', getattr(rd.opts, 'auto_reload_port', 0), get_html, 'content-server/index.html', getattr(rd.opts, 'auto_reload_port', 0),
USERNAME=json.dumps(rd.username), ENTRY_POINT='book list', ENTRY_POINT='book list', LOADING_MSG=prepare_string_for_xml(_('Loading library, please wait'))))
LOADING_MSG=prepare_string_for_xml(_('Loading library, please wait'))))

View File

@ -15,6 +15,7 @@ from threading import Lock
from calibre.db.cache import Cache from calibre.db.cache import Cache
from calibre.db.legacy import create_backend, LibraryDatabase from calibre.db.legacy import create_backend, LibraryDatabase
from calibre.srv.routes import Router from calibre.srv.routes import Router
from calibre.srv.users import UserManager
from calibre.utils.date import utcnow from calibre.utils.date import utcnow
def init_library(library_path): def init_library(library_path):
@ -85,6 +86,7 @@ class Context(object):
self.library_broker = LibraryBroker(libraries) self.library_broker = LibraryBroker(libraries)
self.testing = testing self.testing = testing
self.lock = Lock() self.lock = Lock()
self.user_manager = UserManager()
def init_session(self, endpoint, data): def init_session(self, endpoint, data):
pass pass

66
src/calibre/srv/users.py Normal file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import (unicode_literals, division, absolute_import,
print_function)
import os, json
import apsw
from calibre.constants import config_dir
from calibre.utils.config import to_json, from_json
class UserManager(object):
@property
def conn(self):
if self._conn is None:
self._conn = apsw.Connection(self.path)
c = self._conn.cursor()
uv = next(c.execute('PRAGMA foreign_keys = ON; PRAGMA user_version'))[0]
if uv == 0:
c.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
salt TEXT NOT NULL,
hashed_pw TEXT NOT NULL,
hash_type TEXT NOT NULL,
creation_date INTEGER NOT NULL,
UNIQUE(name)
);
CREATE TABLE session_data (
id INTEGER PRIMARY KEY,
user INTEGER NOT NULL,
data TEXT NOT NULL,
UNIQUE(user),
FOREIGN KEY user REFERENCES users.id
);
PRAGMA user_version=1;
''')
c.close()
def __init__(self):
self.path = os.path.join(config_dir, 'server-users.sqlite')
self._conn = None
def get_session_data(self, username):
for data, in self.conn.cursor().execute(
'SELECT data FROM session_data INNER JOIN users ON (session_data.user = users.id) WHERE users.name=?', (username,)):
try:
return json.loads(data, object_hook=from_json)
except Exception:
pass
return {}
def set_session_data(self, username, data):
conn = self.conn
c = conn.cursor()
for user_id, in c.execute('SELECT id FROM users WHERE name=?', (username,)):
data = json.dumps(data, ensure_ascii=False, default=to_json)
c.execute('UPDATE session_data SET data=? WHERE user=?', (data, user_id))
if not conn.changes():
c.execute('INSERT INTO session_data (data,user) VALUES (?,?)', (data, user_id))

View File

@ -4,7 +4,7 @@
from ajax import ajax from ajax import ajax
from elementmaker import E from elementmaker import E
from gettext import gettext as _ from gettext import gettext as _
from session import UserSessionData from session import UserSessionData, SessionData
from book_list.boss import Boss from book_list.boss import Boss
from book_list.globals import set_boss, set_session_data from book_list.globals import set_boss, set_session_data
@ -13,6 +13,7 @@ def on_library_loaded(end_type, xhr, ev):
p.parentNode.removeChild(p) p.parentNode.removeChild(p)
if end_type == 'load': if end_type == 'load':
interface_data = JSON.parse(xhr.responseText) interface_data = JSON.parse(xhr.responseText)
set_session_data(UserSessionData(interface_data['username']))
# TODO: Copy any user specific session data from the server to # TODO: Copy any user specific session data from the server to
# the local session data object, overriding local data # the local session data object, overriding local data
boss = Boss(interface_data) boss = Boss(interface_data)
@ -28,11 +29,8 @@ def on_library_load_progress(loaded, total):
p.value = loaded p.value = loaded
def load_book_list(): def load_book_list():
sd = set_session_data(UserSessionData(window.calibre_username)) temp = SessionData() # So that settings for anonymous users are preserved
query = {} query = {'library_id':temp.get('library_id'), 'sort':temp.get('sort')}
if not sd.has_user:
# For authenticated users use the session data cached on the server
query = {'library_id':sd.get('library_id'), 'sort':sd.get('sort')}
ajax('ajax/interface-data', on_library_loaded, on_library_load_progress, query=query).send() ajax('ajax/interface-data', on_library_loaded, on_library_load_progress, query=query).send()
def on_load(): def on_load():