Added fix for python2 regex

Fix for python2 attributeError instead of TypeError on login with wrong openLDAP setting
Added default empty string on LDAPCertificate
Fix ldap as scheme for tls connection
Enabled add user on LDAP Authentication
LDAP config port is now number input
Added header for user import config
Added python ldap version to about section
Fix: It's no longer possible to login via fallback password as long as LDAP server is available
Fix: TypeError on bind is now catched and transformed to error message
Update Readme
Fixes for ldap
This commit is contained in:
Ozzieisaacs 2020-04-13 22:23:58 +02:00
parent a194216568
commit 4749eccfa5
12 changed files with 815 additions and 389 deletions

View File

@ -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 - Send eBooks to Kindle devices with the click of a button
- Sync your Kobo devices through Calibre-Web with your Calibre library - 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) - Support for reading eBooks directly in the browser (.txt, .epub, .pdf, .cbr, .cbt, .cbz)
- Upload new books in many formats - Upload new books in many formats, including audio formats (.mp3, .m4a, .m4b)
- Support for Calibre custom columns - Support for Calibre Custom Columns
- Ability to hide content based on categories and Custom Column content per user - Ability to hide content based on categories and Custom Column content per user
- Self-update capability - Self-update capability
- "Magic Link" login to make it easy to log on eReaders - "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 ## Quick start

View File

@ -69,6 +69,7 @@ _VERSIONS = OrderedDict(
pytz=pytz.__version__, pytz=pytz.__version__,
Unidecode = unidecode_version, Unidecode = unidecode_version,
Flask_SimpleLDAP = u'installed' if bool(services.ldap) else u'not installed', 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', Goodreads = u'installed' if bool(services.goodreads_support) else u'not installed',
jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else u'not installed', jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else u'not installed',
) )

View File

@ -504,7 +504,7 @@ def _configuration_update_helper():
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings: with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
gdrive_secrets = json.load(settings)['web'] gdrive_secrets = json.load(settings)['web']
if not gdrive_secrets: 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( gdriveutils.update_settings(
gdrive_secrets['client_id'], gdrive_secrets['client_id'],
gdrive_secrets['client_secret'], gdrive_secrets['client_secret'],
@ -520,11 +520,11 @@ def _configuration_update_helper():
reboot_required |= _config_string("config_keyfile") reboot_required |= _config_string("config_keyfile")
if config.config_keyfile and not os.path.isfile(config.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") reboot_required |= _config_string("config_certfile")
if config.config_certfile and not os.path.isfile(config.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_uploading")
_config_checkbox_int("config_anonbrowse") _config_checkbox_int("config_anonbrowse")
@ -542,45 +542,57 @@ def _configuration_update_helper():
#LDAP configurator, #LDAP configurator,
if config.config_login_type == constants.LOGIN_LDAP: if config.config_login_type == constants.LOGIN_LDAP:
_config_string("config_ldap_provider_url") reboot_required |= _config_string("config_ldap_provider_url")
_config_int("config_ldap_port") reboot_required |= _config_int("config_ldap_port")
# _config_string("config_ldap_schema") # _config_string("config_ldap_schema")
_config_string("config_ldap_dn") reboot_required |= _config_string("config_ldap_dn")
_config_string("config_ldap_user_object") 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 \ if not config.config_ldap_provider_url \
or not config.config_ldap_port \ or not config.config_ldap_port \
or not config.config_ldap_dn \ or not config.config_ldap_dn \
or not config.config_ldap_user_object: or not config.config_ldap_user_object:
return _configuration_result('Please enter a LDAP provider, ' return _configuration_result(_('Please Enter a LDAP Provider, '
'port, DN and user object identifier', gdriveError) '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: 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) 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_ssl")
#_config_checkbox("config_ldap_use_tls") #_config_checkbox("config_ldap_use_tls")
_config_int("config_ldap_encryption") # reboot_required |= _config_checkbox("config_ldap_openldap")
_config_checkbox("config_ldap_openldap")
# _config_checkbox("config_ldap_require_cert") # _config_checkbox("config_ldap_require_cert")
_config_string("config_ldap_cert_path")
if config.config_ldap_group_object_filter.count("%s") != 1: if config.config_ldap_group_object_filter:
return _configuration_result('LDAP Group Object Filter Needs to Have One "%s" Format Identifier', if config.config_ldap_group_object_filter.count("%s") != 1:
gdriveError) 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: 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) gdriveError)
if config.config_ldap_cert_path and not os.path.isfile(config.config_ldap_cert_path): if config.config_ldap_cert_path and not os.path.isdir(config.config_ldap_cert_path):
return _configuration_result('LDAP Certfile location is not valid, please enter correct path', gdriveError) return _configuration_result(_('LDAP Certificate Location is not Valid, Please Enter Correct Path'),
gdriveError)
# Remote login configuration # Remote login configuration
_config_checkbox("config_remote_login") _config_checkbox("config_remote_login")
@ -628,12 +640,12 @@ def _configuration_update_helper():
reboot_required |= _config_int("config_log_level") reboot_required |= _config_int("config_log_level")
reboot_required |= _config_string("config_logfile") reboot_required |= _config_string("config_logfile")
if not logger.is_valid_logfile(config.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_checkbox_int("config_access_log")
reboot_required |= _config_string("config_access_logfile") reboot_required |= _config_string("config_access_logfile")
if not logger.is_valid_logfile(config.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 # Rarfile Content configuration
_config_string("config_rarfile_location") _config_string("config_rarfile_location")
@ -652,7 +664,7 @@ def _configuration_update_helper():
if db_change: if db_change:
# reload(db) # reload(db)
if not db.setup_db(config): 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() config.save()
flash(_(u"Calibre-Web configuration updated"), category="success") 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 show_login_button = config.db_configured and not current_user.is_authenticated
if error_flash: if error_flash:
config.load() config.load()
flash(_(error_flash), category="error") flash(error_flash, category="error")
show_login_button = False show_login_button = False
return render_title_template("config_edit.html", config=config, provider=oauthblueprints, return render_title_template("config_edit.html", config=config, provider=oauthblueprints,

View File

@ -73,7 +73,7 @@ class _Settings(_Base):
config_kobo_sync = Column(Boolean, default=False) config_kobo_sync = Column(Boolean, default=False)
config_default_role = Column(SmallInteger, default=0) 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_columns_to_ignore = Column(String)
config_denied_tags = Column(String, default="") config_denied_tags = Column(String, default="")
@ -99,11 +99,11 @@ class _Settings(_Base):
config_ldap_port = Column(SmallInteger, default=389) config_ldap_port = Column(SmallInteger, default=389)
# config_ldap_schema = Column(String, default='ldap') # config_ldap_schema = Column(String, default='ldap')
config_ldap_serv_username = Column(String, default='cn=admin,dc=example,dc=org') 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_encryption = Column(SmallInteger, default=0)
# config_ldap_use_tls = Column(Boolean, default=False) # config_ldap_use_tls = Column(Boolean, default=False)
# config_ldap_require_cert = 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_dn = Column(String, default='dc=example,dc=org')
config_ldap_user_object = Column(String, default='uid=%s') config_ldap_user_object = Column(String, default='uid=%s')
config_ldap_openldap = Column(Boolean, default=True) config_ldap_openldap = Column(Boolean, default=True)
@ -285,7 +285,9 @@ class _ConfigSQL(object):
self._session.commit() self._session.commit()
self.load() self.load()
def invalidate(self): def invalidate(self, error=None):
if error:
log.error(error)
log.warning("invalidating configuration") log.warning("invalidating configuration")
self.db_configured = False self.db_configured = False
self.config_calibre_dir = None self.config_calibre_dir = None

View File

@ -344,14 +344,13 @@ def setup_db(config):
isolation_level="SERIALIZABLE", isolation_level="SERIALIZABLE",
connect_args={'check_same_thread': False}) connect_args={'check_same_thread': False})
conn = engine.connect() conn = engine.connect()
except: # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
config.invalidate() except Exception as e:
config.invalidate(e)
return False return False
config.db_configured = True config.db_configured = True
update_title_sort(config, conn.connection) update_title_sort(config, conn.connection)
# conn.connection.create_function('lower', 1, lcase)
# conn.connection.create_function('upper', 1, ucase)
if not cc_classes: if not cc_classes:
cc = conn.execute("SELECT id, datatype FROM custom_columns") cc = conn.execute("SELECT id, datatype FROM custom_columns")

View File

@ -30,10 +30,13 @@ except ImportError as err:
goodreads_support = None goodreads_support = None
try: from . import simpleldap as ldap try:
from . import simpleldap as ldap
from .simpleldap import ldapVersion
except ImportError as err: except ImportError as err:
log.debug("cannot import simpleldap, logging in with ldap will not work: %s", err) log.debug("cannot import simpleldap, logging in with ldap will not work: %s", err)
ldap = None ldap = None
ldapVersion = None
try: try:
from . import SyncToken as SyncToken from . import SyncToken as SyncToken

View File

@ -23,6 +23,10 @@ from flask_simpleldap import LDAP, LDAPException
from .. import constants, logger from .. import constants, logger
try:
from ldap.pkginfo import __version__ as ldapVersion
except ImportError:
pass
log = logger.create() log = logger.create()
_ldap = LDAP() _ldap = LDAP()
@ -34,14 +38,16 @@ def init_app(app, config):
app.config['LDAP_HOST'] = config.config_ldap_provider_url app.config['LDAP_HOST'] = config.config_ldap_provider_url
app.config['LDAP_PORT'] = config.config_ldap_port app.config['LDAP_PORT'] = config.config_ldap_port
if config.config_ldap_encryption: if config.config_ldap_encryption == 2:
app.config['LDAP_SCHEMA'] = 'ldaps' app.config['LDAP_SCHEMA'] = 'ldaps'
else: else:
app.config['LDAP_SCHEMA'] = 'ldap' app.config['LDAP_SCHEMA'] = 'ldap'
# app.config['LDAP_SCHEMA'] = config.config_ldap_schema # app.config['LDAP_SCHEMA'] = config.config_ldap_schema
app.config['LDAP_USERNAME'] = config.config_ldap_serv_username 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) 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_REQUIRE_CERT'] = True
app.config['LDAP_CERT_PATH'] = config.config_ldap_cert_path app.config['LDAP_CERT_PATH'] = config.config_ldap_cert_path
app.config['LDAP_BASE_DN'] = config.config_ldap_dn 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_OPENLDAP'] = bool(config.config_ldap_openldap)
app.config['LDAP_GROUP_OBJECT_FILTER'] = config.config_ldap_group_object_filter 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_GROUP_MEMBERS_FIELD'] = config.config_ldap_group_members_field
# app.config['LDAP_CUSTOM_OPTIONS'] = {'OPT_NETWORK_TIMEOUT': 10}
_ldap.init_app(app) _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. :returns: True if login succeeded, False if login failed, None if server unavailable.
''' '''
try: try:
result = _ldap.bind_user(username, password) if _ldap.get_object_details(username):
log.debug("LDAP login '%s': %r", username, result) result = _ldap.bind_user(username, password)
return result is not None 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: except LDAPException as ex:
if ex.message == 'Invalid credentials': if ex.message == 'Invalid credentials':
log.info("LDAP login '%s' failed: %s", username, ex) error = ("LDAP admin login failed")
return False return None, error
if ex.message == "Can't contact LDAP server": if ex.message == "Can't contact LDAP server":
log.warning('LDAP Server down: %s', ex) # log.warning('LDAP Server down: %s', ex)
return None error = ('LDAP Server down: %s' % ex)
return None, error
else: else:
log.warning('LDAP Server error: %s', ex.message) error = ('LDAP Server error: %s' % ex.message)
return None return None, error

View File

@ -35,9 +35,8 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</table> </table>
{% if not (config.config_login_type == 1) %}
<div class="btn btn-default" id="admin_new_user"><a href="{{url_for('admin.new_user')}}">{{_('Add New User')}}</a></div> <div class="btn btn-default" id="admin_new_user"><a href="{{url_for('admin.new_user')}}">{{_('Add New User')}}</a></div>
{% else %} {% if (config.config_login_type == 1) %}
<div class="btn btn-default" id="import_ldap_users" data-toggle="modal" data-target="#StatusDialog">{{_('Import LDAP Users')}}</div> <div class="btn btn-default" id="import_ldap_users" data-toggle="modal" data-target="#StatusDialog">{{_('Import LDAP Users')}}</div>
<!--a href="#" id="import_ldap_users" name="import_ldap_users"><button type="submit" class="btn btn-default">{{_('Import LDAP Users')}}</button></a--> <!--a href="#" id="import_ldap_users" name="import_ldap_users"><button type="submit" class="btn btn-default">{{_('Import LDAP Users')}}</button></a-->
{% endif %} {% endif %}

View File

@ -230,18 +230,11 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="config_ldap_port">{{_('LDAP Server Port')}}</label> <label for="config_ldap_port">{{_('LDAP Server Port')}}</label>
<input type="text" class="form-control" id="config_ldap_port" name="config_ldap_port" value="{% if config.config_ldap_port != None %}{{ config.config_ldap_port }}{% endif %}" autocomplete="off"> <input type="number" min="1" max="65535" class="form-control" id="config_ldap_port" name="config_ldap_port" value="{% if config.config_ldap_port != None %}{{ config.config_ldap_port }}{% endif %}" autocomplete="off" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="config_ldap_serv_username">{{_('LDAP Administrator Username')}}</label> <label for="config_ldap_encryption">{{_('LDAP Encryption')}}</label>
<input type="text" class="form-control" id="config_ldap_serv_username" name="config_ldap_serv_username" value="{% if config.config_ldap_serv_username != None %}{{ config.config_ldap_serv_username }}{% endif %}" autocomplete="off"> <label for="config_ldap_encryption">{{_('LDAP Encryption')}}</label>
</div>
<div class="form-group">
<label for="config_ldap_serv_password">{{_('LDAP Administrator Password')}}</label>
<input type="password" class="form-control" id="config_ldap_serv_password" name="config_ldap_serv_password" value="" autocomplete="off">
</div>
<div class="form-group">
<label for="config_ldap_encryption">{{_('Encryption')}}</label>
<select name="config_ldap_encryption" id="config_ldap_encryption" class="form-control" data-controlall="ldap-cert-settings"> <select name="config_ldap_encryption" id="config_ldap_encryption" class="form-control" data-controlall="ldap-cert-settings">
<option value="0" {% if config.config_ldap_encryption == 0 %}selected{% endif %}>{{ _('None') }}</option> <option value="0" {% if config.config_ldap_encryption == 0 %}selected{% endif %}>{{ _('None') }}</option>
<option value="1" {% if config.config_ldap_encryption == 1 %}selected{% endif %}>{{ _('TLS') }}</option> <option value="1" {% if config.config_ldap_encryption == 1 %}selected{% endif %}>{{ _('TLS') }}</option>
@ -250,10 +243,19 @@
</div> </div>
<div data-related="ldap-cert-settings"> <div data-related="ldap-cert-settings">
<div class="form-group"> <div class="form-group">
<label for="config_ldap_cert_path">{{_('LDAP SSL Certificate Path')}}</label> <label for="config_ldap_cert_path">{{_('LDAP Certificate Path')}}</label>
<input type="text" class="form-control" id="config_ldap_cert_path" name="config_ldap_cert_path" value="{% if config.config_ldap_cert_path != None %}{{ config.config_ldap_cert_path }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_ldap_cert_path" name="config_ldap_cert_path" value="{% if config.config_ldap_cert_path != None %}{{ config.config_ldap_cert_path }}{% endif %}" autocomplete="off">
</div> </div>
</div> </div>
<div class="form-group">
<label for="config_ldap_serv_username">{{_('LDAP Administrator Username')}}</label>
<input type="text" class="form-control" id="config_ldap_serv_username" name="config_ldap_serv_username" value="{% if config.config_ldap_serv_username != None %}{{ config.config_ldap_serv_username }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_ldap_serv_password">{{_('LDAP Administrator Password')}}</label>
<input type="password" class="form-control" id="config_ldap_serv_password" name="config_ldap_serv_password" value="" autocomplete="off">
</div>
<div class="form-group"> <div class="form-group">
<label for="config_ldap_dn">{{_('LDAP Distinguished Name (DN)')}}</label> <label for="config_ldap_dn">{{_('LDAP Distinguished Name (DN)')}}</label>
<input type="text" class="form-control" id="config_ldap_dn" name="config_ldap_dn" value="{% if config.config_ldap_dn != None %}{{ config.config_ldap_dn }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_ldap_dn" name="config_ldap_dn" value="{% if config.config_ldap_dn != None %}{{ config.config_ldap_dn }}{% endif %}" autocomplete="off">
@ -266,18 +268,19 @@
<input type="checkbox" id="config_ldap_openldap" name="config_ldap_openldap" {% if config.config_ldap_openldap %}checked{% endif %}> <input type="checkbox" id="config_ldap_openldap" name="config_ldap_openldap" {% if config.config_ldap_openldap %}checked{% endif %}>
<label for="config_ldap_openldap">{{_('LDAP Server is OpenLDAP?')}}</label> <label for="config_ldap_openldap">{{_('LDAP Server is OpenLDAP?')}}</label>
</div> </div>
<div class="form-group"> <h4 class="text-center">{{_('Following Settings are Needed For User Import')}}</h4>
<label for="config_ldap_group_object_filter">{{_('LDAP Group Object Filter')}}</label> <div class="form-group">
<input type="text" class="form-control" id="config_ldap_group_object_filter" name="config_ldap_group_object_filter" value="{% if config.config_ldap_group_object_filter != None %}{{ config.config_ldap_group_object_filter }}{% endif %}" autocomplete="off"> <label for="config_ldap_group_object_filter">{{_('LDAP Group Object Filter')}}</label>
</div> <input type="text" class="form-control" id="config_ldap_group_object_filter" name="config_ldap_group_object_filter" value="{% if config.config_ldap_group_object_filter != None %}{{ config.config_ldap_group_object_filter }}{% endif %}" autocomplete="off">
<div class="form-group"> </div>
<label for="config_ldap_group_members_field">{{_('LDAP Group Members Field')}}</label> <div class="form-group">
<input type="text" class="form-control" id="config_ldap_group_members_field" name="config_ldap_group_members_field" value="{% if config.config_ldap_group_members_field != None %}{{ config.config_ldap_group_members_field }}{% endif %}" autocomplete="off"> <label for="config_ldap_group_name">{{_('LDAP Group Name')}}</label>
</div> <input type="text" class="form-control" id="config_ldap_group_name" name="config_ldap_group_name" value="{% if config.config_ldap_group_name != None %}{{ config.config_ldap_group_name }}{% endif %}" autocomplete="off">
<div class="form-group"> </div>
<label for="config_ldap_group_name">{{_('LDAP Group Name')}}</label> <div class="form-group">
<input type="text" class="form-control" id="config_ldap_group_name" name="config_ldap_group_name" value="{% if config.config_ldap_group_name != None %}{{ config.config_ldap_group_name }}{% endif %}" autocomplete="off"> <label for="config_ldap_group_members_field">{{_('LDAP Group Members Field')}}</label>
</div> <input type="text" class="form-control" id="config_ldap_group_members_field" name="config_ldap_group_members_field" value="{% if config.config_ldap_group_members_field != None %}{{ config.config_ldap_group_members_field }}{% endif %}" autocomplete="off">
</div>
</div> </div>
{% endif %} {% endif %}
{% if feature_support['oauth'] %} {% if feature_support['oauth'] %}

View File

@ -232,7 +232,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
self.sidebar_view = data.sidebar_view self.sidebar_view = data.sidebar_view
self.default_language = data.default_language self.default_language = data.default_language
self.locale = data.locale self.locale = data.locale
self.mature_content = data.mature_content # self.mature_content = data.mature_content
self.kindle_mail = data.kindle_mail self.kindle_mail = data.kindle_mail
self.denied_tags = data.denied_tags self.denied_tags = data.denied_tags
self.allowed_tags = data.allowed_tags self.allowed_tags = data.allowed_tags
@ -441,14 +441,12 @@ def migrate_Database(session):
"locale VARCHAR(2)," "locale VARCHAR(2),"
"sidebar_view INTEGER," "sidebar_view INTEGER,"
"default_language VARCHAR(3)," "default_language VARCHAR(3),"
"mature_content BOOLEAN,"
"UNIQUE (nickname)," "UNIQUE (nickname),"
"UNIQUE (email)," "UNIQUE (email))")
"CHECK (mature_content IN (0, 1)))")
conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale," conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
"sidebar_view, default_language, mature_content) " "sidebar_view, default_language) "
"SELECT id, nickname, email, role, password, kindle_mail, locale," "SELECT id, nickname, email, role, password, kindle_mail, locale,"
"sidebar_view, default_language, mature_content FROM user") "sidebar_view, default_language FROM user")
# delete old user table and rename new user_id table to user: # delete old user table and rename new user_id table to user:
conn.execute("DROP TABLE user") conn.execute("DROP TABLE user")
conn.execute("ALTER TABLE user_id RENAME TO user") conn.execute("ALTER TABLE user_id RENAME TO user")

View File

@ -28,6 +28,7 @@ import json
import mimetypes import mimetypes
import traceback import traceback
import binascii import binascii
import re
from babel import Locale as LC from babel import Locale as LC
from babel.dates import format_date from babel.dates import format_date
@ -278,31 +279,66 @@ def import_ldap_users():
showtext = {} showtext = {}
try: try:
new_users = services.ldap.get_group_members(config.config_ldap_group_name) new_users = services.ldap.get_group_members(config.config_ldap_group_name)
except services.ldap.LDAPException as e: except (services.ldap.LDAPException, TypeError, AttributeError) as e:
log.debug(e) log.debug(e)
showtext['text'] = _(u'Error : %(ldaperror)s', ldaperror=e) showtext['text'] = _(u'Error: %(ldaperror)s', ldaperror=e)
return json.dumps(showtext)
if not new_users:
log.debug('LDAP empty response')
showtext['text'] = _(u'Error: No user returned in response of LDAP server')
return json.dumps(showtext) return json.dumps(showtext)
for username in new_users: for username in new_users:
user_data = services.ldap.get_object_details(user=username, group=None, query_filter=None, dn_only=False) user = username.decode('utf-8')
content = ub.User() if '=' in user:
content.nickname = username match = re.search("([a-zA-Z0-9-]+)=%s", config.config_ldap_user_object, re.IGNORECASE | re.UNICODE)
content.password = username # dummy password which will be replaced by ldap one if match:
content.email = user_data['mail'][0] match_filter = match.group(1)
if (len(user_data['mail']) > 1): match = re.search(match_filter + "=([[\d\w-]+)", user, re.IGNORECASE | re.UNICODE)
content.kindle_mail = user_data['mail'][1] if match:
content.role = config.config_default_role user = match.group(1)
content.sidebar_view = config.config_default_show else:
content.mature_content = bool(config.config_default_show & constants.MATURE_CONTENT) log.warning("Could Not Parse LDAP User: %s", user)
ub.session.add(content) continue
try: else:
ub.session.commit() log.warning("Could Not Parse LDAP User: %s", user)
except Exception as e: continue
log.warning("Failed to create LDAP user: %s - %s", username, e) if ub.session.query(ub.User).filter(ub.User.nickname == user.lower()).first():
ub.session.rollback() log.warning("LDAP User: %s Already in Database", user)
showtext['text'] = _(u'Failed to create at least one LDAP user') continue
user_data = services.ldap.get_object_details(user=user,
group=None,
query_filter=None,
dn_only=False)
if user_data:
content = ub.User()
content.nickname = user
content.password = '' # dummy password which will be replaced by ldap one
if 'mail' in user_data:
content.email = user_data['mail'][0].decode('utf-8')
if (len(user_data['mail']) > 1):
content.kindle_mail = user_data['mail'][1].decode('utf-8')
else:
log.debug('No Mail Field Found in LDAP Response')
content.email = user + '@email.com'
content.role = config.config_default_role
content.sidebar_view = config.config_default_show
content.allowed_tags = config.config_allowed_tags
content.denied_tags = config.config_denied_tags
content.allowed_column_value = config.config_allowed_column_value
content.denied_column_value = config.config_denied_column_value
ub.session.add(content)
try:
ub.session.commit()
except Exception as e:
log.warning("Failed to create LDAP user: %s - %s", user, e)
ub.session.rollback()
showtext['text'] = _(u'Failed to Create at Least One LDAP User')
else:
log.warning("LDAP User: %s Not Found", user)
showtext['text'] = _(u'At Least One LDAP User Not Found in Database')
if not showtext: if not showtext:
showtext['text'] = _(u'User successfully imported') showtext['text'] = _(u'User Successfully Imported')
return json.dumps(showtext) return json.dumps(showtext)
@ -844,8 +880,9 @@ def reconnect():
@web.route("/search", methods=["GET"]) @web.route("/search", methods=["GET"])
@login_required_if_no_ano @login_required_if_no_ano
def search(): def search():
term = request.args.get("query").strip().lower() term = request.args.get("query")
if term: if term:
term.strip().lower()
entries = get_search_results(term) entries = get_search_results(term)
ids = list() ids = list()
for element in entries: for element in entries:
@ -1175,24 +1212,27 @@ def login():
form = request.form.to_dict() form = request.form.to_dict()
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == form['username'].strip().lower())\ user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == form['username'].strip().lower())\
.first() .first()
if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user: if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user and form['password'] != "":
login_result = services.ldap.bind_user(form['username'], form['password']) login_result, error = services.ldap.bind_user(form['username'], form['password'])
# None if credentials are not matching
# -1 if LDAP Server error
# 0 if wrong passwort
if login_result: if login_result:
login_user(user, remember=True) login_user(user, remember=True)
log.debug(u"You are now logged in as: '%s'", user.nickname) log.debug(u"You are now logged in as: '%s'", user.nickname)
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname),
category="success") category="success")
return redirect_back(url_for("web.index")) return redirect_back(url_for("web.index"))
elif user and check_password_hash(str(user.password), form['password']) and user.nickname != "Guest": elif login_result is None and user and check_password_hash(str(user.password), form['password']) and user.nickname != "Guest":
login_user(user, remember=True) login_user(user, remember=True)
log.info("LDAP Server Down, Fallback Login as: %(nickname)s", user.nickname) log.info("Local Fallback Login as: '%s'", user.nickname)
flash(_(u"LDAP Server Down, Fallback Login as: '%(nickname)s'", flash(_(u"Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known",
nickname=user.nickname), nickname=user.nickname),
category="warning") category="warning")
return redirect_back(url_for("web.index")) return redirect_back(url_for("web.index"))
elif login_result is None: elif login_result is None:
log.info("Could not login. LDAP server down") log.info(error)
flash(_(u"Could not login. LDAP server down, please contact your administrator"), category="error") flash(_(u"Could not login: %(message)s", message=error), category="error")
else: else:
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
log.info('LDAP Login failed for user "%s" IP-adress: %s', form['username'], ipAdress) log.info('LDAP Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)

910
test/Calibre-Web TestSummary.html Normal file → Executable file

File diff suppressed because it is too large Load Diff