diff --git a/README.md b/README.md index 849cd1fd..88db2126 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,12 @@ Calibre-Web is a web app providing a clean interface for browsing, reading and d - Send eBooks to Kindle devices with the click of a button - Sync your Kobo devices through Calibre-Web with your Calibre library - Support for reading eBooks directly in the browser (.txt, .epub, .pdf, .cbr, .cbt, .cbz) -- Upload new books in many formats -- Support for Calibre custom columns +- Upload new books in many formats, including audio formats (.mp3, .m4a, .m4b) +- Support for Calibre Custom Columns - Ability to hide content based on categories and Custom Column content per user - Self-update capability - "Magic Link" login to make it easy to log on eReaders -- Login via google/github oauth and via proxy authentication +- Login via LDAP, google/github oauth and via proxy authentication ## Quick start diff --git a/cps/about.py b/cps/about.py index fd52ca7b..dfc6c502 100644 --- a/cps/about.py +++ b/cps/about.py @@ -69,6 +69,7 @@ _VERSIONS = OrderedDict( pytz=pytz.__version__, Unidecode = unidecode_version, Flask_SimpleLDAP = u'installed' if bool(services.ldap) else u'not installed', + python_LDAP = services.ldapVersion if bool(services.ldapVersion) else u'not installed', Goodreads = u'installed' if bool(services.goodreads_support) else u'not installed', jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else u'not installed', ) diff --git a/cps/admin.py b/cps/admin.py index 726071d3..b2f740bc 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -504,7 +504,7 @@ def _configuration_update_helper(): with open(gdriveutils.CLIENT_SECRETS, 'r') as settings: gdrive_secrets = json.load(settings)['web'] if not gdrive_secrets: - return _configuration_result('client_secrets.json is not configured for web application') + return _configuration_result(_('client_secrets.json Is Not Configured For Web Application')) gdriveutils.update_settings( gdrive_secrets['client_id'], gdrive_secrets['client_secret'], @@ -520,11 +520,11 @@ def _configuration_update_helper(): reboot_required |= _config_string("config_keyfile") if config.config_keyfile and not os.path.isfile(config.config_keyfile): - return _configuration_result('Keyfile location is not valid, please enter correct path', gdriveError) + return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'), gdriveError) reboot_required |= _config_string("config_certfile") if config.config_certfile and not os.path.isfile(config.config_certfile): - return _configuration_result('Certfile location is not valid, please enter correct path', gdriveError) + return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'), gdriveError) _config_checkbox_int("config_uploading") _config_checkbox_int("config_anonbrowse") @@ -542,45 +542,57 @@ def _configuration_update_helper(): #LDAP configurator, if config.config_login_type == constants.LOGIN_LDAP: - _config_string("config_ldap_provider_url") - _config_int("config_ldap_port") + reboot_required |= _config_string("config_ldap_provider_url") + reboot_required |= _config_int("config_ldap_port") # _config_string("config_ldap_schema") - _config_string("config_ldap_dn") - _config_string("config_ldap_user_object") + reboot_required |= _config_string("config_ldap_dn") + reboot_required |= _config_string("config_ldap_serv_username") + reboot_required |= _config_string("config_ldap_user_object") + reboot_required |= _config_string("config_ldap_group_object_filter") + reboot_required |= _config_string("config_ldap_group_members_field") + reboot_required |= _config_checkbox("config_ldap_openldap") + reboot_required |= _config_int("config_ldap_encryption") + reboot_required |= _config_string("config_ldap_cert_path") + _config_string("config_ldap_group_name") + if "config_ldap_serv_password" in to_save and to_save["config_ldap_serv_password"] != "": + reboot_required |= 1 + config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8') + config.save() + if not config.config_ldap_provider_url \ or not config.config_ldap_port \ or not config.config_ldap_dn \ or not config.config_ldap_user_object: - return _configuration_result('Please enter a LDAP provider, ' - 'port, DN and user object identifier', gdriveError) + return _configuration_result(_('Please Enter a LDAP Provider, ' + 'Port, DN and User Object Identifier'), gdriveError) - _config_string("config_ldap_serv_username") - if "config_ldap_serv_password" in to_save and to_save["config_ldap_serv_password"]: - config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8') - if not config.config_ldap_serv_username and not config.config_ldap_serv_password: - return _configuration_result('Please enter a LDAP service account and password', gdriveError) + if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password): + return _configuration_result('Please Enter a LDAP Service Account and Password', gdriveError) - _config_string("config_ldap_group_object_filter") - _config_string("config_ldap_group_members_field") - _config_string("config_ldap_group_name") #_config_checkbox("config_ldap_use_ssl") #_config_checkbox("config_ldap_use_tls") - _config_int("config_ldap_encryption") - _config_checkbox("config_ldap_openldap") + # reboot_required |= _config_checkbox("config_ldap_openldap") # _config_checkbox("config_ldap_require_cert") - _config_string("config_ldap_cert_path") - if config.config_ldap_group_object_filter.count("%s") != 1: - return _configuration_result('LDAP Group Object Filter Needs to Have One "%s" Format Identifier', - gdriveError) + if config.config_ldap_group_object_filter: + if config.config_ldap_group_object_filter.count("%s") != 1: + return _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'), + gdriveError) + if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"): + return _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'), + gdriveError) if config.config_ldap_user_object.count("%s") != 1: - return _configuration_result('LDAP User Object Filter needs to Have One "%s" Format Identifier', + return _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'), + gdriveError) + if config.config_ldap_user_object.count("(") != config.config_ldap_user_object.count(")"): + return _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'), gdriveError) - if config.config_ldap_cert_path and not os.path.isfile(config.config_ldap_cert_path): - return _configuration_result('LDAP Certfile location is not valid, please enter correct path', gdriveError) + if config.config_ldap_cert_path and not os.path.isdir(config.config_ldap_cert_path): + return _configuration_result(_('LDAP Certificate Location is not Valid, Please Enter Correct Path'), + gdriveError) # Remote login configuration _config_checkbox("config_remote_login") @@ -628,12 +640,12 @@ def _configuration_update_helper(): reboot_required |= _config_int("config_log_level") reboot_required |= _config_string("config_logfile") if not logger.is_valid_logfile(config.config_logfile): - return _configuration_result('Logfile location is not valid, please enter correct path', gdriveError) + return _configuration_result(_('Logfile Location is not Valid, Please Enter Correct Path'), gdriveError) reboot_required |= _config_checkbox_int("config_access_log") reboot_required |= _config_string("config_access_logfile") if not logger.is_valid_logfile(config.config_access_logfile): - return _configuration_result('Access Logfile location is not valid, please enter correct path', gdriveError) + return _configuration_result(_('Access Logfile Location is not Valid, Please Enter Correct Path'), gdriveError) # Rarfile Content configuration _config_string("config_rarfile_location") @@ -652,7 +664,7 @@ def _configuration_update_helper(): if db_change: # reload(db) if not db.setup_db(config): - return _configuration_result('DB location is not valid, please enter correct path', gdriveError) + return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdriveError) config.save() flash(_(u"Calibre-Web configuration updated"), category="success") @@ -678,7 +690,7 @@ def _configuration_result(error_flash=None, gdriveError=None): show_login_button = config.db_configured and not current_user.is_authenticated if error_flash: config.load() - flash(_(error_flash), category="error") + flash(error_flash, category="error") show_login_button = False return render_title_template("config_edit.html", config=config, provider=oauthblueprints, diff --git a/cps/config_sql.py b/cps/config_sql.py index 98b30416..7fc99a91 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -73,7 +73,7 @@ class _Settings(_Base): config_kobo_sync = Column(Boolean, default=False) config_default_role = Column(SmallInteger, default=0) - config_default_show = Column(SmallInteger, default=6143) + config_default_show = Column(SmallInteger, default=constants.ADMIN_USER_SIDEBAR) config_columns_to_ignore = Column(String) config_denied_tags = Column(String, default="") @@ -99,11 +99,11 @@ class _Settings(_Base): config_ldap_port = Column(SmallInteger, default=389) # config_ldap_schema = Column(String, default='ldap') config_ldap_serv_username = Column(String, default='cn=admin,dc=example,dc=org') - config_ldap_serv_password = Column(String) + config_ldap_serv_password = Column(String, default="") config_ldap_encryption = Column(SmallInteger, default=0) # config_ldap_use_tls = Column(Boolean, default=False) # config_ldap_require_cert = Column(Boolean, default=False) - config_ldap_cert_path = Column(String) + config_ldap_cert_path = Column(String, default="") config_ldap_dn = Column(String, default='dc=example,dc=org') config_ldap_user_object = Column(String, default='uid=%s') config_ldap_openldap = Column(Boolean, default=True) @@ -285,7 +285,9 @@ class _ConfigSQL(object): self._session.commit() self.load() - def invalidate(self): + def invalidate(self, error=None): + if error: + log.error(error) log.warning("invalidating configuration") self.db_configured = False self.config_calibre_dir = None diff --git a/cps/db.py b/cps/db.py index f40e0cda..966adbe5 100755 --- a/cps/db.py +++ b/cps/db.py @@ -344,14 +344,13 @@ def setup_db(config): isolation_level="SERIALIZABLE", connect_args={'check_same_thread': False}) conn = engine.connect() - except: - config.invalidate() + # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302 + except Exception as e: + config.invalidate(e) return False config.db_configured = True update_title_sort(config, conn.connection) - # conn.connection.create_function('lower', 1, lcase) - # conn.connection.create_function('upper', 1, ucase) if not cc_classes: cc = conn.execute("SELECT id, datatype FROM custom_columns") diff --git a/cps/services/__init__.py b/cps/services/__init__.py index 2eb82f0d..11ef4c65 100644 --- a/cps/services/__init__.py +++ b/cps/services/__init__.py @@ -30,10 +30,13 @@ except ImportError as err: goodreads_support = None -try: from . import simpleldap as ldap +try: + from . import simpleldap as ldap + from .simpleldap import ldapVersion except ImportError as err: log.debug("cannot import simpleldap, logging in with ldap will not work: %s", err) ldap = None + ldapVersion = None try: from . import SyncToken as SyncToken diff --git a/cps/services/simpleldap.py b/cps/services/simpleldap.py index 82690d87..841f61e1 100644 --- a/cps/services/simpleldap.py +++ b/cps/services/simpleldap.py @@ -23,6 +23,10 @@ from flask_simpleldap import LDAP, LDAPException from .. import constants, logger +try: + from ldap.pkginfo import __version__ as ldapVersion +except ImportError: + pass log = logger.create() _ldap = LDAP() @@ -34,14 +38,16 @@ def init_app(app, config): app.config['LDAP_HOST'] = config.config_ldap_provider_url app.config['LDAP_PORT'] = config.config_ldap_port - if config.config_ldap_encryption: + if config.config_ldap_encryption == 2: app.config['LDAP_SCHEMA'] = 'ldaps' else: app.config['LDAP_SCHEMA'] = 'ldap' # app.config['LDAP_SCHEMA'] = config.config_ldap_schema app.config['LDAP_USERNAME'] = config.config_ldap_serv_username + if config.config_ldap_serv_password is None: + config.config_ldap_serv_password = '' app.config['LDAP_PASSWORD'] = base64.b64decode(config.config_ldap_serv_password) - if config.config_ldap_cert_path: + if bool(config.config_ldap_cert_path): app.config['LDAP_REQUIRE_CERT'] = True app.config['LDAP_CERT_PATH'] = config.config_ldap_cert_path app.config['LDAP_BASE_DN'] = config.config_ldap_dn @@ -52,6 +58,7 @@ def init_app(app, config): app.config['LDAP_OPENLDAP'] = bool(config.config_ldap_openldap) app.config['LDAP_GROUP_OBJECT_FILTER'] = config.config_ldap_group_object_filter app.config['LDAP_GROUP_MEMBERS_FIELD'] = config.config_ldap_group_members_field + # app.config['LDAP_CUSTOM_OPTIONS'] = {'OPT_NETWORK_TIMEOUT': 10} _ldap.init_app(app) @@ -78,16 +85,22 @@ def bind_user(username, password): :returns: True if login succeeded, False if login failed, None if server unavailable. ''' try: - result = _ldap.bind_user(username, password) - log.debug("LDAP login '%s': %r", username, result) - return result is not None + if _ldap.get_object_details(username): + result = _ldap.bind_user(username, password) + log.debug("LDAP login '%s': %r", username, result) + return result is not None, None + return None, None # User not found + except (TypeError, AttributeError) as ex: + error = ("LDAP bind_user: %s" % ex) + return None, error except LDAPException as ex: if ex.message == 'Invalid credentials': - log.info("LDAP login '%s' failed: %s", username, ex) - return False + error = ("LDAP admin login failed") + return None, error if ex.message == "Can't contact LDAP server": - log.warning('LDAP Server down: %s', ex) - return None + # log.warning('LDAP Server down: %s', ex) + error = ('LDAP Server down: %s' % ex) + return None, error else: - log.warning('LDAP Server error: %s', ex.message) - return None + error = ('LDAP Server error: %s' % ex.message) + return None, error diff --git a/cps/templates/admin.html b/cps/templates/admin.html index b548f021..ab143c59 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -35,9 +35,8 @@ {% endif %} {% endfor %} - {% if not (config.config_login_type == 1) %}
- {% else %} + {% if (config.config_login_type == 1) %}Start Time: 2020-04-02 20:21:13
+Start Time: 2020-04-13 20:58:27
Stop Time: 2020-04-02 20:57:57
+Stop Time: 2020-04-13 21:36:17
Duration: 1878.33 s
+Duration: 2002.47 s