diff --git a/cps/admin.py b/cps/admin.py index 55a11c3e..90521241 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -43,7 +43,7 @@ from .gdriveutils import is_gdrive_ready, gdrive_support from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano feature_support = { - 'ldap': False, # bool(services.ldap), + 'ldap': bool(services.ldap), 'goodreads': bool(services.goodreads_support), 'kobo': bool(services.kobo) } @@ -542,24 +542,43 @@ def _configuration_update_helper(): if config.config_login_type == constants.LOGIN_LDAP: _config_string("config_ldap_provider_url") _config_int("config_ldap_port") - _config_string("config_ldap_schema") + # _config_string("config_ldap_schema") _config_string("config_ldap_dn") _config_string("config_ldap_user_object") - 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) + 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) _config_string("config_ldap_serv_username") - if not config.config_ldap_serv_username or "config_ldap_serv_password" not in to_save: + 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) - config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode) - - _config_checkbox("config_ldap_use_ssl") - _config_checkbox("config_ldap_use_tls") - _config_checkbox("config_ldap_openldap") - _config_checkbox("config_ldap_require_cert") - _config_string("config_ldap_cert_path") - 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) + + _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") + # _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_user_object.count("%s") != 1: + return _configuration_result('LDAP User Object Filter needs to Have One "%s" Format Identifier', + 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) # Remote login configuration _config_checkbox("config_remote_login") diff --git a/cps/config_sql.py b/cps/config_sql.py index d971f72a..98b30416 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -37,6 +37,8 @@ _Base = declarative_base() class _Settings(_Base): __tablename__ = 'settings' + # config_is_initial = Column(Boolean, default=True) + id = Column(Integer, primary_key=True) mail_server = Column(String, default=constants.DEFAULT_MAIL_SERVER) mail_port = Column(Integer, default=25) @@ -93,18 +95,21 @@ class _Settings(_Base): config_kobo_proxy = Column(Boolean, default=False) - config_ldap_provider_url = Column(String, default='localhost') + config_ldap_provider_url = Column(String, default='example.org') config_ldap_port = Column(SmallInteger, default=389) - config_ldap_schema = Column(String, default='ldap') - config_ldap_serv_username = Column(String) + # 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_use_ssl = Column(Boolean, default=False) - config_ldap_use_tls = Column(Boolean, default=False) - config_ldap_require_cert = Column(Boolean, default=False) + 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_dn = Column(String) - config_ldap_user_object = Column(String) - config_ldap_openldap = Column(Boolean, default=False) + 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) + config_ldap_group_object_filter = Column(String, default='(&(objectclass=posixGroup)(cn=%s))') + config_ldap_group_members_field = Column(String, default='memberUid') + config_ldap_group_name = Column(String, default='calibreweb') config_ebookconverter = Column(Integer, default=0) config_converterpath = Column(String) @@ -212,7 +217,7 @@ class _ConfigSQL(object): return not bool(self.mail_server == constants.DEFAULT_MAIL_SERVER) - def set_from_dictionary(self, dictionary, field, convertor=None, default=None): + def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None): '''Possibly updates a field of this object. The new value, if present, is grabbed from the given dictionary, and optionally passed through a convertor. @@ -228,7 +233,10 @@ class _ConfigSQL(object): return False if convertor is not None: - new_value = convertor(new_value) + if encode: + new_value = convertor(new_value.encode(encode)) + else: + new_value = convertor(new_value) current_value = self.__dict__.get(field) if current_value == new_value: diff --git a/cps/services/simpleldap.py b/cps/services/simpleldap.py index 42a9aacd..82690d87 100644 --- a/cps/services/simpleldap.py +++ b/cps/services/simpleldap.py @@ -34,29 +34,45 @@ def init_app(app, config): app.config['LDAP_HOST'] = config.config_ldap_provider_url app.config['LDAP_PORT'] = config.config_ldap_port - app.config['LDAP_SCHEMA'] = config.config_ldap_schema - app.config['LDAP_USERNAME'] = config.config_ldap_user_object.replace('%s', config.config_ldap_serv_username)\ - + ',' + config.config_ldap_dn + if config.config_ldap_encryption: + 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 app.config['LDAP_PASSWORD'] = base64.b64decode(config.config_ldap_serv_password) - app.config['LDAP_REQUIRE_CERT'] = bool(config.config_ldap_require_cert) - if config.config_ldap_require_cert: + if 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 app.config['LDAP_USER_OBJECT_FILTER'] = config.config_ldap_user_object - app.config['LDAP_USE_SSL'] = bool(config.config_ldap_use_ssl) - app.config['LDAP_USE_TLS'] = bool(config.config_ldap_use_tls) + + app.config['LDAP_USE_TLS'] = bool(config.config_ldap_encryption == 1) + app.config['LDAP_USE_SSL'] = bool(config.config_ldap_encryption == 2) 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 _ldap.init_app(app) +def get_object_details(user=None, group=None, query_filter=None, dn_only=False): + return _ldap.get_object_details(user, group, query_filter, dn_only) + + +def bind(): + return _ldap.bind() + + +def get_group_members(group): + return _ldap.get_group_members(group) + def basic_auth_required(func): return _ldap.basic_auth_required(func) def bind_user(username, password): - # ulf= _ldap.get_object_details('admin') '''Attempts a LDAP login. :returns: True if login succeeded, False if login failed, None if server unavailable. diff --git a/cps/static/js/main.js b/cps/static/js/main.js index bd8d04f9..937709a6 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -29,7 +29,6 @@ $(document).on("change", "input[type=\"checkbox\"][data-control]", function () { }); }); - // Generic control/related handler to show/hide fields based on a select' value $(document).on("change", "select[data-control]", function() { var $this = $(this); @@ -39,13 +38,26 @@ $(document).on("change", "select[data-control]", function() { for (var i = 0; i < $(this)[0].length; i++) { var element = parseInt($(this)[0][i].value); if (element === showOrHide) { - $("[data-related=" + name + "-" + element + "]").show(); + $("[data-related^=" + name + "][data-related*=-" + element + "]").show(); } else { - $("[data-related=" + name + "-" + element + "]").hide(); + $("[data-related^=" + name + "][data-related*=-" + element + "]").hide(); } } }); +// Generic control/related handler to show/hide fields based on a select' value +// this one is made to show all values if select value is not 0 +$(document).on("change", "select[data-controlall]", function() { + var $this = $(this); + var name = $this.data("controlall"); + var showOrHide = parseInt($this.val()); + if (showOrHide) { + $("[data-related=" + name + "]").show(); + } else { + $("[data-related=" + name + "]").hide(); + } +}); + $(function() { var updateTimerID; @@ -214,6 +226,7 @@ $(function() { // Init all data control handlers to default $("input[data-control]").trigger("change"); $("select[data-control]").trigger("change"); + $("select[data-controlall]").trigger("change"); $("#bookDetailsModal") .on("show.bs.modal", function(e) { @@ -274,6 +287,20 @@ $(function() { $(".discover .row").isotope("layout"); }); + $('#import_ldap_users').click(function() { + var pathname = document.getElementsByTagName("script"), src = pathname[pathname.length - 1].src; + var path = src.substring(0, src.lastIndexOf("/")); + /*$.ajax({ + method:"get", + url: path + "/../../import_ldap_users", + });*/ + $.getJSON(path + "/../../import_ldap_users", + function(data) { + location.reload(); + } + ); + }); + $(".author-expand").click(function() { $(this).parent().find("a.author-name").slice($(this).data("authors-max")).toggle(); $(this).parent().find("span.author-hidden-divider").toggle(); diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 69deee09..8cb66c32 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -35,7 +35,12 @@ {% endif %} {% endfor %} -
{{_('Add New User')}}
+ {% if not (config.config_login_type == 1) %} +
{{_('Add New User')}}
+ {% else %} +
{{_('Import LDAP Users')}}
+ + {% endif %} diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 184f4320..5b114739 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -198,6 +198,17 @@ {% endif %} +
+ + +
+
+
+ + +
+
+ {% if not config.config_is_initial %} {% if feature_support['ldap'] or feature_support['oauth'] %}
@@ -211,7 +222,7 @@ {% endif %}
- {% if feature_support['ldap'] %} + {% if feature_support['ldap'] %}
@@ -221,34 +232,26 @@
-
- - -
- -
-
- - +
- - -
-
- - + +
- +
@@ -263,6 +266,18 @@
+
+ + +
+
+ + +
+
+ + +
{% endif %} {% if feature_support['oauth'] %} @@ -282,17 +297,8 @@ {% endfor %} {% endif %} + {% endif %} {% endif %} -
- - -
-
-
- - -
-
diff --git a/cps/web.py b/cps/web.py index 81246172..100a84c4 100644 --- a/cps/web.py +++ b/cps/web.py @@ -53,7 +53,7 @@ from .pagination import Pagination from .redirect import redirect_back feature_support = { - 'ldap': False, # bool(services.ldap), + 'ldap': bool(services.ldap), 'goodreads': bool(services.goodreads_support), 'kobo': bool(services.kobo) } @@ -273,6 +273,36 @@ def before_request(): return redirect(url_for('admin.basic_configuration')) +@app.route('/import_ldap_users') +def import_ldap_users(): + try: + new_users = services.ldap.get_group_members(config.config_ldap_group_name) + except services.ldap.LDAPException as e: + log.debug(e) + return "" + except Exception as e: + print('pass') + + for username in new_users: + user_data = services.ldap.get_object_details(user=username, group=None, query_filter=None, dn_only=False) + content = ub.User() + content.nickname = username + content.password = username # dummy password which will be replaced by ldap one + content.email = user_data['mail'][0] + if (len(user_data['mail']) > 1): + content.kindle_mail = user_data['mail'][1] + content.role = config.config_default_role + content.sidebar_view = config.config_default_show + content.mature_content = bool(config.config_default_show & constants.MATURE_CONTENT) + ub.session.add(content) + try: + ub.session.commit() + except Exception as e: + log.warning("Failed to create LDAP user: %s - %s", username, e) + ub.session.rollback() + return "" + + # ################################### data provider functions ######################################################### @@ -1150,8 +1180,15 @@ def login(): flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") return redirect_back(url_for("web.index")) - if login_result is None: - log.error('Could not login. LDAP server down, please contact your administrator') + elif user and check_password_hash(str(user.password), form['password']) and user.nickname != "Guest": + login_user(user, remember=True) + log.info("LDAP Server Down, Fallback Login as: %(nickname)s", user.nickname) + flash(_(u"LDAP Server Down, Fallback Login as: '%(nickname)s'", + nickname=user.nickname), + category="warning") + return redirect_back(url_for("web.index")) + elif login_result is None: + log.info("Could not login. LDAP server down") flash(_(u"Could not login. LDAP server down, please contact your administrator"), category="error") else: ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) @@ -1166,7 +1203,7 @@ def login(): flash(_(u"New Password was send to your email address"), category="info") log.info('Password reset for user "%s" IP-adress: %s', form['username'], ipAdress) else: - log.info(u"An unknown error occurred. Please try again later.") + log.info(u"An unknown error occurred. Please try again later") flash(_(u"An unknown error occurred. Please try again later."), category="error") else: flash(_(u"Please enter valid username to reset password"), category="error") @@ -1176,6 +1213,7 @@ def login(): login_user(user, remember=True) 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), category="success") + config.config_is_initial = False return redirect_back(url_for("web.index")) else: log.info('Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)