def __init__(self, libraries, opts, testing=False, notify_changes=None): self.opts = opts self.library_broker = libraries if isinstance(libraries, LibraryBroker) else LibraryBroker(libraries) self.testing = testing self.lock = Lock() self.user_manager = UserManager(opts.userdb) self.ignored_fields = frozenset(filter(None, (x.strip() for x in (opts.ignored_fields or '').split(',')))) self.displayed_fields = frozenset(filter(None, (x.strip() for x in (opts.displayed_fields or '').split(',')))) self._notify_changes = notify_changes
def save_changes(self): settings = {} for tab in self.tabs: settings.update(getattr(tab, 'settings', {})) users = self.users_tab.user_data if settings['auth']: if not users: error_dialog( self, _('No users specified'), _( 'You have turned on the setting to require passwords to access' ' the content server, but you have not created any user accounts.' ' Create at least one user account in the "User accounts" tab to proceed.' ), show=True ) self.tabs_widget.setCurrentWidget(self.users_tab) return False if not self.custom_list_tab.commit(): return False if not self.search_net_tab.commit(): return False ConfigWidgetBase.commit(self) change_settings(**settings) UserManager().user_data = users return True
def __init__(self, libraries, opts, testing=False): self.opts = opts self.library_broker = LibraryBroker(libraries) self.testing = testing self.lock = Lock() self.user_manager = UserManager(opts.userdb) self.ignored_fields = frozenset( filter(None, (x.strip() for x in (opts.ignored_fields or '').split(',')))) self.displayed_fields = frozenset( filter(None, (x.strip() for x in (opts.displayed_fields or '').split(','))))
def manage_users_cli(path=None): from calibre.srv.users import UserManager m = UserManager(path) enc = getattr(sys.stdin, 'encoding', preferred_encoding) or preferred_encoding def get_input(prompt): prints(prompt, end=' ') ans = raw_input() if isinstance(ans, bytes): ans = ans.decode(enc) if iswindows: # https://bugs.python.org/issue11272 ans = ans.rstrip('\r') return ans def choice(question=_('What do you want to do?'), choices=(), default=None, banner=''): prints(banner) for i, choice in enumerate(choices): prints('%d)' % (i + 1), choice) print() while True: prompt = question + ' [1-%d]:' % len(choices) if default is not None: prompt = question + ' [1-%d %s: %d]' % ( len(choices), _('default'), default + 1) reply = get_input(prompt) if not reply and default is not None: reply = unicode_type(default + 1) if not reply: prints(_('No choice selected, exiting...')) raise SystemExit(0) reply = reply.strip() try: num = int(reply) - 1 if not (0 <= num < len(choices)): raise Exception('bad num') return num except Exception: prints(_('%s is not a valid choice, try again') % reply) def get_valid(prompt, invalidq=lambda x: None): while True: ans = get_input(prompt + ':').strip() fail_message = invalidq(ans) if fail_message is None: return ans prints(fail_message) def get_valid_user(): prints(_('Existing user names:')) users = sorted(m.all_user_names) if not users: raise SystemExit( _('There are no users, you must first add an user')) prints(', '.join(users)) def validate(username): if not m.has_user(username): return _('The username %s does not exist') % username return get_valid(_('Enter the username'), validate) def get_pass(username): from calibre.utils.unicode_getpass import getpass while True: one = getpass(_('Enter the new password for %s: ') % username) if not one: prints(_('Empty passwords are not allowed')) continue two = getpass( _('Re-enter the new password for %s, to verify: ') % username) if one != two: prints(_('Passwords do not match')) continue msg = m.validate_password(one) if msg is None: return one prints(msg) def add_user(): username = get_valid(_('Enter the username'), m.validate_username) pw = get_pass(username) m.add_user(username, pw) prints(_('User %s added successfully!') % username) def remove_user(): un = get_valid_user() if get_input((_('Are you sure you want to remove the user %s?') % un) + ' [y/n]:') != 'y': raise SystemExit(0) m.remove_user(un) prints(_('User %s successfully removed!') % un) def change_password(username): pw = get_pass(username) m.change_password(username, pw) prints(_('Password for %s successfully changed!') % username) def show_password(username): pw = m.get(username) prints(_('Current password for {0} is: {1}').format(username, pw)) def change_readonly(username): readonly = m.is_readonly(username) if readonly: q = _('Allow {} to make changes (i.e. grant write access)') else: q = _('Prevent {} from making changes (i.e. remove write access)') if get_input(q.format(username) + '? [y/n]:').lower() == 'y': m.set_readonly(username, not readonly) def change_restriction(username): r = m.restrictions(username) if r is None: raise SystemExit('The user {} does not exist'.format(username)) if r['allowed_library_names']: prints( _('{} is currently only allowed to access the libraries named: {}' ).format(username, ', '.join(r['allowed_library_names']))) if r['blocked_library_names']: prints( _('{} is currently not allowed to access the libraries named: {}' ).format(username, ', '.join(r['blocked_library_names']))) if r['library_restrictions']: prints( _('{} has the following additional per-library restrictions:'). format(username)) for k, v in iteritems(r['library_restrictions']): prints(k + ':', v) else: prints( _('{} has no additional per-library restrictions').format( username)) c = choice(choices=[ _('Allow access to all libraries'), _('Allow access to only specified libraries'), _('Allow access to all, except specified libraries'), _('Change per-library restrictions'), _('Cancel') ]) if c == 0: m.update_user_restrictions(username, {}) elif c == 3: while True: library = get_input(_('Enter the name of the library:')) if not library: break prints( _('Enter a search expression, access will be granted only to books matching this expression.' ' An empty expression will grant access to all books.')) plr = get_input(_('Search expression:')) if plr: r['library_restrictions'][library] = plr else: r['library_restrictions'].pop(library, None) m.update_user_restrictions(username, r) if get_input(_('Another restriction?') + ' (y/n):') != 'y': break elif c == 4: pass else: names = get_input( _('Enter a comma separated list of library names:')) names = list(filter(None, [x.strip() for x in names.split(',')])) w = 'allowed_library_names' if c == 1 else 'blocked_library_names' t = _('Allowing access only to libraries: {}') if c == 1 else _( 'Allowing access to all libraries, except: {}') prints(t.format(', '.join(names))) m.update_user_restrictions(username, {w: names}) def edit_user(username=None): username = username or get_valid_user() c = choice(choices=[ _('Show password for {}').format(username), _('Change password for {}').format(username), _('Change read/write permission for {}').format(username), _('Change the libraries {} is allowed to access').format(username), _('Cancel'), ], banner='\n' + _('{0} has {1} access').format( username, _('readonly') if m.is_readonly(username) else _('read-write'))) print() if c > 3: actions.append(toplevel) return { 0: show_password, 1: change_password, 2: change_readonly, 3: change_restriction }[c](username) actions.append(partial(edit_user, username=username)) def toplevel(): { 0: add_user, 1: edit_user, 2: remove_user, 3: lambda: None }[choice(choices=[ _('Add a new user'), _('Edit an existing user'), _('Remove a user'), _('Cancel') ])]() actions = [toplevel] while actions: actions[0]() del actions[0]
class Context(object): log = None url_for = None jobs_manager = None CATEGORY_CACHE_SIZE = 25 SEARCH_CACHE_SIZE = 100 def __init__(self, libraries, opts, testing=False, notify_changes=None): self.opts = opts self.library_broker = libraries if isinstance(libraries, LibraryBroker) else LibraryBroker(libraries) self.testing = testing self.lock = Lock() self.user_manager = UserManager(opts.userdb) self.ignored_fields = frozenset(filter(None, (x.strip() for x in (opts.ignored_fields or '').split(',')))) self.displayed_fields = frozenset(filter(None, (x.strip() for x in (opts.displayed_fields or '').split(',')))) self._notify_changes = notify_changes def notify_changes(self, library_path, change_event): if self._notify_changes is not None: self._notify_changes(library_path, change_event) def start_job(self, name, module, func, args=(), kwargs=None, job_done_callback=None, job_data=None): return self.jobs_manager.start_job(name, module, func, args, kwargs, job_done_callback, job_data) def job_status(self, job_id): return self.jobs_manager.job_status(job_id) def is_field_displayable(self, field): if self.displayed_fields and field not in self.displayed_fields: return False return field not in self.ignored_fields def init_session(self, endpoint, data): pass def finalize_session(self, endpoint, data, output): pass def get_library(self, request_data, library_id=None): if not request_data.username: return self.library_broker.get(library_id) lf = partial(self.user_manager.allowed_library_names, request_data.username) allowed_libraries = self.library_broker.allowed_libraries(lf) if not allowed_libraries: raise HTTPForbidden('The user {} is not allowed to access any libraries on this server'.format(request_data.username)) library_id = library_id or next(allowed_libraries.iterkeys()) if library_id in allowed_libraries: return self.library_broker.get(library_id) raise HTTPForbidden('The user {} is not allowed to access the library {}'.format(request_data.username, library_id)) def library_info(self, request_data): if not request_data.username: return self.library_broker.library_map, self.library_broker.default_library lf = partial(self.user_manager.allowed_library_names, request_data.username) allowed_libraries = self.library_broker.allowed_libraries(lf) if not allowed_libraries: raise HTTPForbidden('The user {} is not allowed to access any libraries on this server'.format(request_data.username)) return dict(allowed_libraries), next(allowed_libraries.iterkeys()) def restriction_for(self, request_data, db): return self.user_manager.library_restriction(request_data.username, path_for_db(db)) def has_id(self, request_data, db, book_id): restriction = self.restriction_for(request_data, db) if restriction: return book_id in db.search('', restriction=restriction) return db.has_id(book_id) def allowed_book_ids(self, request_data, db): restriction = self.restriction_for(request_data, db) if restriction: return frozenset(db.search('', restriction=restriction)) return db.all_book_ids() def check_for_write_access(self, request_data): if not request_data.username: if request_data.is_local_connection and self.opts.local_write: return raise HTTPForbidden('Anonymous users are not allowed to make changes') if self.user_manager.is_readonly(request_data.username): raise HTTPForbidden('The user {} does not have permission to make changes'.format(request_data.username)) def get_effective_book_ids(self, db, request_data, vl): return db.books_in_virtual_library(vl, self.restriction_for(request_data, db)) def get_categories(self, request_data, db, sort='name', first_letter_sort=True, vl=''): restrict_to_ids = self.get_effective_book_ids(db, request_data, vl) key = restrict_to_ids, sort, first_letter_sort with self.lock: cache = self.library_broker.category_caches[db.server_library_id] old = cache.pop(key, None) if old is None or old[0] <= db.last_modified(): categories = db.get_categories(book_ids=restrict_to_ids, sort=sort, first_letter_sort=first_letter_sort) cache[key] = old = (utcnow(), categories) if len(cache) > self.CATEGORY_CACHE_SIZE: cache.popitem(last=False) else: cache[key] = old return old[1] def get_tag_browser(self, request_data, db, opts, render, vl=''): restrict_to_ids = self.get_effective_book_ids(db, request_data, vl) key = restrict_to_ids, opts with self.lock: cache = self.library_broker.category_caches[db.server_library_id] old = cache.pop(key, None) if old is None or old[0] <= db.last_modified(): categories = db.get_categories(book_ids=restrict_to_ids, sort=opts.sort_by, first_letter_sort=opts.collapse_model == 'first letter') data = json.dumps(render(db, categories), ensure_ascii=False) if isinstance(data, type('')): data = data.encode('utf-8') cache[key] = old = (utcnow(), data) if len(cache) > self.CATEGORY_CACHE_SIZE: cache.popitem(last=False) else: cache[key] = old return old[1] def search(self, request_data, db, query, vl=''): restrict_to_ids = self.get_effective_book_ids(db, request_data, vl) query = query or '' key = query, restrict_to_ids with self.lock: cache = self.library_broker.search_caches[db.server_library_id] old = cache.pop(key, None) if old is None or old[0] < db.clear_search_cache_count: matches = db.search(query, book_ids=restrict_to_ids) cache[key] = old = (db.clear_search_cache_count, matches) if len(cache) > self.SEARCH_CACHE_SIZE: cache.popitem(last=False) else: cache[key] = old return old[1]
class Context(object): log = None url_for = None jobs_manager = None CATEGORY_CACHE_SIZE = 25 SEARCH_CACHE_SIZE = 100 def __init__(self, libraries, opts, testing=False, notify_changes=None): self.opts = opts self.library_broker = libraries if isinstance( libraries, LibraryBroker) else LibraryBroker(libraries) self.testing = testing self.lock = Lock() self.user_manager = UserManager(opts.userdb) self.ignored_fields = frozenset( filter(None, (x.strip() for x in (opts.ignored_fields or '').split(',')))) self.displayed_fields = frozenset( filter(None, (x.strip() for x in (opts.displayed_fields or '').split(',')))) self._notify_changes = notify_changes def notify_changes(self, library_path, change_event): if self._notify_changes is not None: self._notify_changes(library_path, change_event) def start_job(self, name, module, func, args=(), kwargs=None, job_done_callback=None, job_data=None): return self.jobs_manager.start_job(name, module, func, args, kwargs, job_done_callback, job_data) def job_status(self, job_id): return self.jobs_manager.job_status(job_id) def is_field_displayable(self, field): if self.displayed_fields and field not in self.displayed_fields: return False return field not in self.ignored_fields def init_session(self, endpoint, data): pass def finalize_session(self, endpoint, data, output): pass def get_library(self, request_data, library_id=None): if not request_data.username: return self.library_broker.get(library_id) lf = partial(self.user_manager.allowed_library_names, request_data.username) allowed_libraries = self.library_broker.allowed_libraries(lf) if not allowed_libraries: raise HTTPForbidden( 'The user {} is not allowed to access any libraries on this server' .format(request_data.username)) library_id = library_id or next(allowed_libraries.iterkeys()) if library_id in allowed_libraries: return self.library_broker.get(library_id) raise HTTPForbidden( 'The user {} is not allowed to access the library {}'.format( request_data.username, library_id)) def library_info(self, request_data): if not request_data.username: return self.library_broker.library_map, self.library_broker.default_library lf = partial(self.user_manager.allowed_library_names, request_data.username) allowed_libraries = self.library_broker.allowed_libraries(lf) if not allowed_libraries: raise HTTPForbidden( 'The user {} is not allowed to access any libraries on this server' .format(request_data.username)) return dict(allowed_libraries), next(allowed_libraries.iterkeys()) def restriction_for(self, request_data, db): return self.user_manager.library_restriction(request_data.username, path_for_db(db)) def has_id(self, request_data, db, book_id): restriction = self.restriction_for(request_data, db) if restriction: return book_id in db.search('', restriction=restriction) return db.has_id(book_id) def allowed_book_ids(self, request_data, db): restriction = self.restriction_for(request_data, db) if restriction: return frozenset(db.search('', restriction=restriction)) return db.all_book_ids() def check_for_write_access(self, request_data): if not request_data.username: if request_data.is_local_connection and self.opts.local_write: return raise HTTPForbidden( 'Anonymous users are not allowed to make changes') if self.user_manager.is_readonly(request_data.username): raise HTTPForbidden( 'The user {} does not have permission to make changes'.format( request_data.username)) def get_effective_book_ids(self, db, request_data, vl): return db.books_in_virtual_library( vl, self.restriction_for(request_data, db)) def get_categories(self, request_data, db, sort='name', first_letter_sort=True, vl=''): restrict_to_ids = self.get_effective_book_ids(db, request_data, vl) key = restrict_to_ids, sort, first_letter_sort with self.lock: cache = self.library_broker.category_caches[db.server_library_id] old = cache.pop(key, None) if old is None or old[0] <= db.last_modified(): categories = db.get_categories( book_ids=restrict_to_ids, sort=sort, first_letter_sort=first_letter_sort) cache[key] = old = (utcnow(), categories) if len(cache) > self.CATEGORY_CACHE_SIZE: cache.popitem(last=False) else: cache[key] = old return old[1] def get_tag_browser(self, request_data, db, opts, render, vl=''): restrict_to_ids = self.get_effective_book_ids(db, request_data, vl) key = restrict_to_ids, opts with self.lock: cache = self.library_broker.category_caches[db.server_library_id] old = cache.pop(key, None) if old is None or old[0] <= db.last_modified(): categories = db.get_categories( book_ids=restrict_to_ids, sort=opts.sort_by, first_letter_sort=opts.collapse_model == 'first letter') data = json.dumps(render(db, categories), ensure_ascii=False) if isinstance(data, type('')): data = data.encode('utf-8') cache[key] = old = (utcnow(), data) if len(cache) > self.CATEGORY_CACHE_SIZE: cache.popitem(last=False) else: cache[key] = old return old[1] def search(self, request_data, db, query, vl=''): restrict_to_ids = self.get_effective_book_ids(db, request_data, vl) query = query or '' key = query, restrict_to_ids with self.lock: cache = self.library_broker.search_caches[db.server_library_id] old = cache.pop(key, None) if old is None or old[0] < db.clear_search_cache_count: matches = db.search(query, book_ids=restrict_to_ids) cache[key] = old = (db.clear_search_cache_count, matches) if len(cache) > self.SEARCH_CACHE_SIZE: cache.popitem(last=False) else: cache[key] = old return old[1]
def manage_users(path=None): from calibre.srv.users import UserManager m = UserManager(path) enc = getattr(sys.stdin, 'encoding', preferred_encoding) or preferred_encoding def choice(question, choices, default=None, banner=''): prints(banner) for i, choice in enumerate(choices): prints('%d)' % (i+1), choice) print() while True: prompt = question + ' [1-%d]: ' % len(choices) if default is not None: prompt = question + ' [1-%d %s: %d]' % (len(choices), _('default'), default+1) reply = raw_input(prompt) if not reply and default is not None: reply = str(default + 1) if not reply: raise SystemExit(0) reply = reply.strip() try: num = int(reply) - 1 if not (0 <= num < len(choices)): raise Exception('bad num') return num except Exception: prints(_('%s is not a valid choice, try again') % reply) def get_valid(prompt, invalidq=lambda x: None): while True: ans = raw_input(prompt + ': ').strip().decode(enc) fail_message = invalidq(ans) if fail_message is None: return ans prints(fail_message) def get_valid_user(): prints(_('Existing user names:')) users = sorted(m.all_user_names) if not users: raise SystemExit(_('There are no users, you must first add an user')) prints(', '.join(users)) def validate(username): if not m.has_user(username): return _('The username %s does not exist') % username return get_valid(_('Enter the username'), validate) def get_pass(username): while True: from getpass import getpass one = getpass(_('Enter the new password for %s: ') % username).decode(enc) if not one: prints(_('Empty passwords are not allowed')) continue two = getpass(_('Re-enter the new password for %s, to verify: ') % username).decode(enc) if one != two: prints(_('Passwords do not match')) continue msg = m.validate_password(one) if msg is None: return one prints(msg) def add_user(): username = get_valid(_('Enter the username'), m.validate_username) pw = get_pass(username) m.add_user(username, pw) prints(_('User %s added successfully!') % username) def remove_user(): un = get_valid_user() if raw_input((_('Are you sure you want to remove the user %s?') % un) + ' [y/n]: ').decode(enc) != 'y': raise SystemExit(0) m.remove_user(un) prints(_('User %s successfully removed!') % un) def edit_user(): username = get_valid_user() pw = get_pass(username) m.change_password(username, pw) prints(_('Password for %s successfully changed!') % username) def show_password(): username = get_valid_user() pw = m.get(username) prints(_('Password for {0} is: {1}').format(username, pw)) {0:add_user, 1:edit_user, 2:remove_user, 3:show_password}[choice(_('What do you want to do?'), [ _('Add a new user'), _('Edit an existing user'), _('Remove a user'), _('Show the password for a user')])]()
def genesis(self): self.user_data = UserManager().user_data self.user_list.addItems(sorted(self.user_data, key=primary_sort_key)) self.user_list.setCurrentRow(0) self.user_list.currentItemChanged.connect(self.current_item_changed) self.current_item_changed()
class Users(QWidget): changed_signal = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QHBoxLayout(self) self.lp = lp = QVBoxLayout() l.addLayout(lp) self.h = h = QHBoxLayout() lp.addLayout(h) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('&Add user'), self) b.clicked.connect(self.add_user) h.addWidget(b) self.remove_button = b = QPushButton( QIcon(I('minus.png')), _('&Remove user'), self ) b.clicked.connect(self.remove_user) h.addStretch(2), h.addWidget(b) self.user_list = w = QListWidget(self) w.setSpacing(1) w.doubleClicked.connect(self.current_user_activated) w.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) lp.addWidget(w) self.user_display = u = User(self) u.changed_signal.connect(self.changed_signal.emit) l.addWidget(u) def genesis(self): self.user_data = UserManager().user_data self.user_list.addItems(sorted(self.user_data, key=primary_sort_key)) self.user_list.setCurrentRow(0) self.user_list.currentItemChanged.connect(self.current_item_changed) self.current_item_changed() def current_user_activated(self): self.user_display.change_password() def current_item_changed(self): item = self.user_list.currentItem() if item is None: username = None else: username = item.text() if username not in self.user_data: username = None self.display_user_data(username) def add_user(self): d = NewUser(self.user_data, parent=self) if d.exec_() == d.Accepted: un, pw = d.username, d.password self.user_data[un] = create_user_data(pw) self.user_list.insertItem(0, un) self.user_list.setCurrentRow(0) self.display_user_data(un) self.changed_signal.emit() def remove_user(self): u = self.user_list.currentItem() if u is not None: self.user_list.takeItem(self.user_list.row(u)) un = u.text() self.user_data.pop(un, None) self.changed_signal.emit() self.current_item_changed() def display_user_data(self, username=None): self.user_display.show_user(username, self.user_data)
def __init__(self, libraries, opts, testing=False): self.opts = opts self.library_broker = LibraryBroker(libraries) self.testing = testing self.lock = Lock() self.user_manager = UserManager(opts.userdb)