Merge remote-tracking branch 'ldap/master'

# Conflicts:
#	cps/admin.py
#	cps/templates/admin.html
#	cps/templates/config_edit.html
#	cps/web.py
This commit is contained in:
Ozzieisaacs 2020-04-05 17:31:41 +02:00
commit 8bee424cc0
7 changed files with 187 additions and 68 deletions

View File

@ -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:
return _configuration_result('Please enter a LDAP service account and password', gdriveError)
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode)
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')
_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)
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_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")

View File

@ -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:

View File

@ -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.

View File

@ -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();

View File

@ -35,7 +35,12 @@
{% endif %}
{% endfor %}
</table>
<div class="btn btn-default" id="admin_new_user"><a href="{{url_for('admin.new_user')}}">{{_('Add New User')}}</a></div>
{% 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>
{% else %}
<div class="btn btn-default" id="import_ldap_users">{{_('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-->
{% endif %}
</div>
</div>

View File

@ -198,6 +198,17 @@
</div>
</div>
{% endif %}
<div class="form-group">
<input type="checkbox" id="config_allow_reverse_proxy_header_login" name="config_allow_reverse_proxy_header_login" data-control="reverse-proxy-login-settings" {% if config.config_allow_reverse_proxy_header_login %}checked{% endif %}>
<label for="config_allow_reverse_proxy_header_login">{{_('Allow Reverse Proxy Authentication')}}</label>
</div>
<div data-related="reverse-proxy-login-settings">
<div class="form-group">
<label for="config_reverse_proxy_login_header_name">{{_('Reverse Proxy Header Name')}}</label>
<input type="text" class="form-control" id="config_reverse_proxy_login_header_name" name="config_reverse_proxy_login_header_name" value="{% if config.config_reverse_proxy_login_header_name != None %}{{ config.config_reverse_proxy_login_header_name }}{% endif %}" autocomplete="off">
</div>
</div>
{% if not config.config_is_initial %}
{% if feature_support['ldap'] or feature_support['oauth'] %}
<div class="form-group">
<label for="config_login_type">{{_('Login type')}}</label>
@ -211,7 +222,7 @@
{% endif %}
</select>
</div>
{% if feature_support['ldap'] %}
{% if feature_support['ldap'] %}
<div data-related="login-settings-1">
<div class="form-group">
<label for="config_ldap_provider_url">{{_('LDAP Server Host Name or IP Address')}}</label>
@ -221,34 +232,26 @@
<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">
</div>
<div class="form-group">
<label for="config_ldap_schema">{{_('LDAP Schema (LDAP or LPAPS)')}}</label>
<input type="text" class="form-control" id="config_ldap_schema" name="config_ldap_schema" value="{% if config.config_ldap_schema != None %}{{ config.config_ldap_schema }}{% endif %}" autocomplete="off">
</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="{% if config.config_ldap_serv_password != None %}{{ config.config_ldap_serv_password }}{% endif %}" autocomplete="off">
<input type="password" class="form-control" id="config_ldap_serv_password" name="config_ldap_serv_password" value="" autocomplete="off">
</div>
<div class="form-group">
<input type="checkbox" id="config_ldap_use_ssl" name="config_ldap_use_ssl" {% if config.config_ldap_use_ssl %}checked{% endif %}>
<label for="config_ldap_use_ssl">{{_('LDAP Server Enable SSL')}}</label>
</div>
<div class="form-group">
<input type="checkbox" id="config_ldap_use_tls" name="config_ldap_use_tls" {% if config.config_ldap_use_tls %}checked{% endif %}>
<label for="config_ldap_use_tls">{{_('LDAP Server Enable TLS')}}</label>
</div>
<div class="form-group">
<input type="checkbox" id="config_ldap_require_cert" name="config_ldap_require_cert" data-control="ldap-cert-settings" {% if config.config_ldap_require_cert %}checked{% endif %}>
<label for="config_ldap_require_cert">{{_('LDAP Server Certificate')}}</label>
<label for="config_ldap_encryption">{{_('Encryption')}}</label>
<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="1" {% if config.config_ldap_encryption == 1 %}selected{% endif %}>{{ _('TLS') }}</option>
<option value="2" {% if config.config_ldap_encryption == 2 %}selected{% endif %}>{{ _('SSL') }}</option>
</select>
</div>
<div data-related="ldap-cert-settings">
<div class="form-group">
<label for="config_ldap_cert_path">{{_('LDAP SSL 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 and config.config_ldap_require_cert !=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 class="form-group">
@ -263,6 +266,18 @@
<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>
</div>
<div class="form-group">
<label for="config_ldap_group_object_filter">{{_('LDAP Group Object Filter')}}</label>
<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>
<div class="form-group">
<label for="config_ldap_group_members_field">{{_('LDAP Group Members Field')}}</label>
<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 class="form-group">
<label for="config_ldap_group_name">{{_('LDAP Group Name')}}</label>
<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>
</div>
{% endif %}
{% if feature_support['oauth'] %}
@ -282,17 +297,8 @@
{% endfor %}
</div>
{% endif %}
{% endif %}
{% endif %}
<div class="form-group">
<input type="checkbox" id="config_allow_reverse_proxy_header_login" name="config_allow_reverse_proxy_header_login" data-control="reverse-proxy-login-settings" {% if config.config_allow_reverse_proxy_header_login %}checked{% endif %}>
<label for="config_allow_reverse_proxy_header_login">{{_('Allow Reverse Proxy Authentication')}}</label>
</div>
<div data-related="reverse-proxy-login-settings">
<div class="form-group">
<label for="config_reverse_proxy_login_header_name">{{_('Reverse Proxy Header Name')}}</label>
<input type="text" class="form-control" id="config_reverse_proxy_login_header_name" name="config_reverse_proxy_login_header_name" value="{% if config.config_reverse_proxy_login_header_name != None %}{{ config.config_reverse_proxy_login_header_name }}{% endif %}" autocomplete="off">
</div>
</div>
</div>
</div>
</div>

View File

@ -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)