calibre-server --manage-users: Allow managing users while the server is running

This commit is contained in:
Kovid Goyal 2021-12-01 11:42:17 +05:30
parent 872613f5f3
commit 0b532f6974
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 15 additions and 26 deletions

View File

@ -197,7 +197,6 @@ def main(args=sys.argv):
except NoAutoReload as e: except NoAutoReload as e:
raise SystemExit(error_message(e)) raise SystemExit(error_message(e))
ensure_single_instance()
if opts.userdb: if opts.userdb:
opts.userdb = os.path.abspath(os.path.expandvars(os.path.expanduser(opts.userdb))) opts.userdb = os.path.abspath(os.path.expandvars(os.path.expanduser(opts.userdb)))
connect(opts.userdb, exc_class=SystemExit).close() connect(opts.userdb, exc_class=SystemExit).close()
@ -207,6 +206,7 @@ def main(args=sys.argv):
except (KeyboardInterrupt, EOFError): except (KeyboardInterrupt, EOFError):
raise SystemExit(_('Interrupted by user')) raise SystemExit(_('Interrupted by user'))
raise SystemExit(0) raise SystemExit(0)
ensure_single_instance()
libraries = args[1:] libraries = args[1:]
for lib in libraries: for lib in libraries:

View File

@ -3,14 +3,16 @@
# License: GPLv3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
import os, json, re
from threading import RLock
import apsw import apsw
import json
import os
import re
from functools import lru_cache
from threading import RLock
from calibre import as_unicode from calibre import as_unicode
from calibre.constants import config_dir from calibre.constants import config_dir
from calibre.utils.config import to_json, from_json from calibre.utils.config import from_json, to_json
from polyglot.builtins import iteritems from polyglot.builtins import iteritems
@ -25,6 +27,7 @@ def load_json(raw):
return {} return {}
@lru_cache(maxsize=1024)
def parse_restriction(raw): def parse_restriction(raw):
r = load_json(raw) r = load_json(raw)
if not isinstance(r, dict): if not isinstance(r, dict):
@ -65,7 +68,7 @@ def validate_password(pw):
def create_user_data(pw, readonly=False, restriction=None): def create_user_data(pw, readonly=False, restriction=None):
return { return {
'pw':pw, 'restriction':parse_restriction(restriction or '{}'), 'readonly': readonly 'pw':pw, 'restriction':parse_restriction(restriction or '{}').copy(), 'readonly': readonly
} }
@ -125,8 +128,6 @@ class UserManager:
def __init__(self, path=None): def __init__(self, path=None):
self.path = os.path.join(config_dir, 'server-users.sqlite') if path is None else path self.path = os.path.join(config_dir, 'server-users.sqlite') if path is None else path
self._conn = None self._conn = None
self._restrictions = {}
self._readonly = {}
def get_session_data(self, username): def get_session_data(self, username):
with self.lock: with self.lock:
@ -210,26 +211,19 @@ class UserManager:
self.refresh() self.refresh()
def refresh(self): def refresh(self):
self._restrictions.clear() pass # legacy compat
self._readonly.clear()
def is_readonly(self, username): def is_readonly(self, username):
with self.lock: with self.lock:
try:
return self._readonly[username]
except KeyError:
self._readonly[username] = False
for readonly, in self.conn.cursor().execute( for readonly, in self.conn.cursor().execute(
'SELECT readonly FROM users WHERE name=?', (username,)): 'SELECT readonly FROM users WHERE name=?', (username,)):
self._readonly[username] = readonly == 'y' return readonly == 'y'
return self._readonly[username] return False
return False
def set_readonly(self, username, value): def set_readonly(self, username, value):
with self.lock: with self.lock:
self.conn.cursor().execute( self.conn.cursor().execute(
'UPDATE users SET readonly=? WHERE name=?', ('y' if value else 'n', username)) 'UPDATE users SET readonly=? WHERE name=?', ('y' if value else 'n', username))
self._readonly.pop(username, None)
def change_password(self, username, pw): def change_password(self, username, pw):
with self.lock: with self.lock:
@ -241,13 +235,9 @@ class UserManager:
def restrictions(self, username): def restrictions(self, username):
with self.lock: with self.lock:
r = self._restrictions.get(username) for restriction, in self.conn.cursor().execute(
if r is None: 'SELECT restriction FROM users WHERE name=?', (username,)):
for restriction, in self.conn.cursor().execute( return parse_restriction(restriction).copy()
'SELECT restriction FROM users WHERE name=?', (username,)):
self._restrictions[username] = r = parse_restriction(restriction)
break
return r
def allowed_library_names(self, username, all_library_names): def allowed_library_names(self, username, all_library_names):
' Get allowed library names for specified user from set of all library names ' ' Get allowed library names for specified user from set of all library names '
@ -266,7 +256,6 @@ class UserManager:
if not isinstance(restrictions, dict): if not isinstance(restrictions, dict):
raise TypeError('restrictions must be a dict') raise TypeError('restrictions must be a dict')
with self.lock: with self.lock:
self._restrictions.pop(username, None)
self.conn.cursor().execute( self.conn.cursor().execute(
'UPDATE users SET restriction=? WHERE name=?', (serialize_restriction(restrictions), username)) 'UPDATE users SET restriction=? WHERE name=?', (serialize_restriction(restrictions), username))