Ldap working

This commit is contained in:
Ozzieisaacs 2019-02-17 09:09:20 +01:00
parent 37007dafee
commit a0be02e687
7 changed files with 135 additions and 146 deletions

View File

@ -39,7 +39,6 @@ from sqlalchemy.exc import IntegrityError
from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders
import helper import helper
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
from oauth_bb import oauth_check
try: try:
from urllib.parse import quote from urllib.parse import quote
from imp import reload from imp import reload
@ -59,6 +58,19 @@ try:
except ImportError: except ImportError:
feature_support['rar'] = False feature_support['rar'] = False
try:
import ldap
feature_support['ldap'] = True
except ImportError:
feature_support['ldap'] = False
try:
from oauth_bb import oauth_check
feature_support['oauth'] = True
except ImportError:
feature_support['oauth'] = False
oauth_check = {}
feature_support['gdrive'] = gdrive_support feature_support['gdrive'] = gdrive_support
admi = Blueprint('admin', __name__) admi = Blueprint('admin', __name__)
@ -335,7 +347,7 @@ def configuration_helper(origin):
flash(_(u'client_secrets.json is not configured for web application'), category="error") flash(_(u'client_secrets.json is not configured for web application'), category="error")
return render_title_template("config_edit.html", content=config, origin=origin, return render_title_template("config_edit.html", content=config, origin=origin,
gdriveError=gdriveError, gdriveError=gdriveError,
goodreads=goodreads_support, title=_(u"Basic Configuration"), gfeature_support=feature_support, title=_(u"Basic Configuration"),
page="config") page="config")
# always show google drive settings, but in case of error deny support # always show google drive settings, but in case of error deny support
if "config_use_google_drive" in to_save and not gdriveError: if "config_use_google_drive" in to_save and not gdriveError:
@ -361,7 +373,7 @@ def configuration_helper(origin):
flash(_(u'Keyfile location is not valid, please enter correct path'), category="error") flash(_(u'Keyfile location is not valid, please enter correct path'), category="error")
return render_title_template("config_edit.html", content=config, origin=origin, return render_title_template("config_edit.html", content=config, origin=origin,
gdriveError=gdriveError, gdriveError=gdriveError,
goodreads=goodreads_support, title=_(u"Basic Configuration"), feature_support=feature_support, title=_(u"Basic Configuration"),
page="config") page="config")
if "config_certfile" in to_save: if "config_certfile" in to_save:
if content.config_certfile != to_save["config_certfile"]: if content.config_certfile != to_save["config_certfile"]:
@ -392,7 +404,7 @@ def configuration_helper(origin):
content.config_ebookconverter = int(to_save["config_ebookconverter"]) content.config_ebookconverter = int(to_save["config_ebookconverter"])
#LDAP configurator, #LDAP configurator,
if "config_use_ldap" in to_save and to_save["config_use_ldap"] == "on": if "config_login_type" in to_save and to_save["config_login_type"] == "1":
if "config_ldap_provider_url" not in to_save or "config_ldap_dn" not in to_save: if "config_ldap_provider_url" not in to_save or "config_ldap_dn" not in to_save:
ub.session.commit() ub.session.commit()
flash(_(u'Please enter a LDAP provider and a DN'), category="error") flash(_(u'Please enter a LDAP provider and a DN'), category="error")
@ -400,7 +412,7 @@ def configuration_helper(origin):
gdriveError=gdriveError, feature_support=feature_support, gdriveError=gdriveError, feature_support=feature_support,
title=_(u"Basic Configuration"), page="config") title=_(u"Basic Configuration"), page="config")
else: else:
content.config_use_ldap = 1 content.config_login_type = ub.LOGIN_LDAP
content.config_ldap_provider_url = to_save["config_ldap_provider_url"] content.config_ldap_provider_url = to_save["config_ldap_provider_url"]
content.config_ldap_dn = to_save["config_ldap_dn"] content.config_ldap_dn = to_save["config_ldap_dn"]
db_change = True db_change = True

View File

@ -1,39 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2018-2019 Krakinou
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ldap
from cps import ub, app, request
from flask import flash, url_for
from redirect import redirect_back
from flask_login import login_user
from flask_babel import gettext as _
def login(form, user):
try:
ub.User.try_login(form['username'], form['password'])
login_user(user, remember=True)
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")
return redirect_back(url_for("web.index"))
except ldap.INVALID_CREDENTIALS:
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
app.logger.info('LDAP Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress)
flash(_(u"Wrong Username or Password"), category="error")
def logout():
pass

View File

@ -20,13 +20,11 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/> # along with this program. If not, see <http://www.gnu.org/licenses/>
try:
from flask_dance.contrib.github import make_github_blueprint, github from flask_dance.contrib.github import make_github_blueprint, github
from flask_dance.contrib.google import make_google_blueprint, google from flask_dance.contrib.google import make_google_blueprint, google
from flask_dance.consumer import oauth_authorized, oauth_error from flask_dance.consumer import oauth_authorized, oauth_error
from oauth import OAuthBackend from oauth import OAuthBackend
except ImportError:
pass
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
from flask import flash, session, redirect, url_for, request, make_response, abort from flask import flash, session, redirect, url_for, request, make_response, abort
import json import json

View File

@ -29,6 +29,23 @@ $(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);
var name = $this.data("control");
var showOrHide = $this.val();
var showOrHideLast = $("#"+name + " option:last").val()
for (i = 0; i < $(this)[0].length; i++){
if (parseInt($(this)[0][i].value) == showOrHide){
$("[data-related=\"" + name + "-" + i + "\"]").show();
} else {
$("[data-related=\"" + name + "-" + i + "\"]").hide();
}
}
});
$(function() { $(function() {
var updateTimerID; var updateTimerID;
var updateText; var updateText;
@ -179,7 +196,9 @@ $(function() {
}); });
}); });
// Init all data control handlers to default
$("input[data-control]").trigger("change"); $("input[data-control]").trigger("change");
$("select[data-control]").trigger("change");
$("#bookDetailsModal") $("#bookDetailsModal")
.on("show.bs.modal", function(e) { .on("show.bs.modal", function(e) {

View File

@ -176,29 +176,34 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if feature_support['ldap'] %} {% if feature_support['ldap'] or feature_support['oauth'] %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" id="config_use_ldap" name="config_use_ldap" data-control="ldap-settings" {% if content.config_use_ldap %}checked{% endif %}> <label for="config_login_type">{{_('Login type')}}</label>
<label for="config_use_ldap">{{_('Use')}} LDAP Authentication</label> <select name="config_login_type" id="config_login_type" class="form-control" data-control="login-settings">
<option value="0" {% if content.config_login_type == 0 %}selected{% endif %}>{{_('Use standard Authentication')}}</option>
{% if feature_support['ldap'] %}
<option value="1" {% if content.config_login_type == 1 %}selected{% endif %}>{{_('Use')}} LDAP Authentication</option>
{% endif %}
{% if feature_support['oauth'] %}
<option value="2" {% if content.config_login_type == 2 %}selected{% endif %}>{{_('Use')}} GitHub OAuth</option>
<option value="3" {% if content.config_login_type == 3 %}selected{% endif %}>{{_('Use')}} GitHub OAuth</option>
{% endif %}
</select>
</div> </div>
<div data-related="ldap-settings"> {% if feature_support['ldap'] %}
<div data-related="login-settings-1">
<div class="form-group"> <div class="form-group">
<label for="config_ldap_provider_url">{{_('LDAP Provider URL')}}</label> <label for="config_ldap_provider_url">{{_('LDAP Provider URL')}}</label>
<input type="text" class="form-control" id="config_ldap_provider_url" name="config_ldap_provider_url" value="{% if content.config_use_ldap != None %}{{ content.config_ldap_provider_url }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_ldap_provider_url" name="config_ldap_provider_url" value="{{ content.config_ldap_provider_url }}" autocomplete="off">
</div> </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 content.config_use_ldap != None %}{{ content.config_ldap_dn }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_ldap_dn" name="config_ldap_dn" value="{{ content.config_ldap_dn }}" autocomplete="off">
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if feature_support['oauth'] %} {% if feature_support['oauth'] %}
<div class="form-group"> <div data-related="login-settings-2">
<input type="checkbox" id="config_use_github_oauth" name="config_use_github_oauth" data-control="github-oauth-settings" {% if content.config_use_github_oauth %}checked{% endif %}>
<label for="config_use_github_oauth">{{_('Use')}} GitHub OAuth</label>
<a href="https://github.com/settings/developers" target="_blank" style="margin-left: 5px">{{_('Obtain GitHub OAuth Credentail')}}</a>
</div>
<div data-related="github-oauth-settings">
<div class="form-group"> <div class="form-group">
<label for="config_github_oauth_client_id">{{_('GitHub OAuth Client Id')}}</label> <label for="config_github_oauth_client_id">{{_('GitHub OAuth Client Id')}}</label>
<input type="text" class="form-control" id="config_github_oauth_client_id" name="config_github_oauth_client_id" value="{% if content.config_github_oauth_client_id != None %}{{ content.config_github_oauth_client_id }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_github_oauth_client_id" name="config_github_oauth_client_id" value="{% if content.config_github_oauth_client_id != None %}{{ content.config_github_oauth_client_id }}{% endif %}" autocomplete="off">
@ -208,12 +213,7 @@
<input type="text" class="form-control" id="config_github_oauth_client_secret" name="config_github_oauth_client_secret" value="{% if content.config_github_oauth_client_secret != None %}{{ content.config_github_oauth_client_secret }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_github_oauth_client_secret" name="config_github_oauth_client_secret" value="{% if content.config_github_oauth_client_secret != None %}{{ content.config_github_oauth_client_secret }}{% endif %}" autocomplete="off">
</div> </div>
</div> </div>
<div class="form-group"> <div data-related="login-settings-3">
<input type="checkbox" id="config_use_google_oauth" name="config_use_google_oauth" data-control="google-oauth-settings" {% if content.config_use_google_oauth %}checked{% endif %}>
<label for="config_use_google_oauth">{{_('Use')}} Google OAuth</label>
<a href="https://console.developers.google.com/apis/credentials" target="_blank" style="margin-left: 5px">{{_('Obtain Google OAuth Credentail')}}</a>
</div>
<div data-related="google-oauth-settings">
<div class="form-group"> <div class="form-group">
<label for="config_google_oauth_client_id">{{_('Google OAuth Client Id')}}</label> <label for="config_google_oauth_client_id">{{_('Google OAuth Client Id')}}</label>
<input type="text" class="form-control" id="config_google_oauth_client_id" name="config_google_oauth_client_id" value="{% if content.config_google_oauth_client_id != None %}{{ content.config_google_oauth_client_id }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_google_oauth_client_id" name="config_google_oauth_client_id" value="{% if content.config_google_oauth_client_id != None %}{{ content.config_google_oauth_client_id }}{% endif %}" autocomplete="off">
@ -224,6 +224,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -34,16 +34,15 @@ import cli
try: try:
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
import ldap
oauth_support = True oauth_support = True
except ImportError: except ImportError:
oauth_support = False oauth_support = False
pass pass
engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False) try:
Base = declarative_base() import ldap
except ImportError:
session = None pass
ROLE_USER = 0 ROLE_USER = 0
ROLE_ADMIN = 1 ROLE_ADMIN = 1
@ -70,6 +69,16 @@ SIDEBAR_SORTED = 1024
MATURE_CONTENT = 2048 MATURE_CONTENT = 2048
SIDEBAR_PUBLISHER = 4096 SIDEBAR_PUBLISHER = 4096
UPDATE_STABLE = 0
AUTO_UPDATE_STABLE = 1
UPDATE_NIGHTLY = 2
AUTO_UPDATE_NIGHTLY = 4
LOGIN_STANDARD = 0
LOGIN_LDAP = 1
LOGIN_OAUTH_GITHUB = 2
LOGIN_OAUTH_GOOGLE = 3
DEFAULT_PASS = "admin123" DEFAULT_PASS = "admin123"
try: try:
DEFAULT_PORT = int(os.environ.get("CALIBRE_PORT", 8083)) DEFAULT_PORT = int(os.environ.get("CALIBRE_PORT", 8083))
@ -78,10 +87,8 @@ except ValueError:
os.environ.get("CALIBRE_PORT", 8083) + ', faling back to default (8083)') os.environ.get("CALIBRE_PORT", 8083) + ', faling back to default (8083)')
DEFAULT_PORT = 8083 DEFAULT_PORT = 8083
UPDATE_STABLE = 0 session = None
AUTO_UPDATE_STABLE = 1
UPDATE_NIGHTLY = 2
AUTO_UPDATE_NIGHTLY = 4
engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False) engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False)
Base = declarative_base() Base = declarative_base()
@ -188,12 +195,12 @@ class UserBase:
def __repr__(self): def __repr__(self):
return '<User %r>' % self.nickname return '<User %r>' % self.nickname
#Login via LDAP method # Login via LDAP method
@staticmethod @staticmethod
def try_login(username, password): def try_login(username, password,config_dn, ldap_provider_url):
conn = get_ldap_connection() conn = get_ldap_connection(ldap_provider_url)
conn.simple_bind_s( conn.simple_bind_s(
config.config_ldap_dn.replace("%s", username), config_dn.replace("%s", username),
password) password)
# Baseclass for Users in Calibre-Web, settings which are depending on certain users are stored here. It is derived from # Baseclass for Users in Calibre-Web, settings which are depending on certain users are stored here. It is derived from
@ -358,13 +365,14 @@ class Settings(Base):
config_use_goodreads = Column(Boolean) config_use_goodreads = Column(Boolean)
config_goodreads_api_key = Column(String) config_goodreads_api_key = Column(String)
config_goodreads_api_secret = Column(String) config_goodreads_api_secret = Column(String)
config_use_ldap = Column(Boolean) config_login_type = Column(Integer, default=0)
# config_use_ldap = Column(Boolean)
config_ldap_provider_url = Column(String) config_ldap_provider_url = Column(String)
config_ldap_dn = Column(String) config_ldap_dn = Column(String)
config_use_github_oauth = Column(Boolean) # config_use_github_oauth = Column(Boolean)
config_github_oauth_client_id = Column(String) config_github_oauth_client_id = Column(String)
config_github_oauth_client_secret = Column(String) config_github_oauth_client_secret = Column(String)
config_use_google_oauth = Column(Boolean) # config_use_google_oauth = Column(Boolean)
config_google_oauth_client_id = Column(String) config_google_oauth_client_id = Column(String)
config_google_oauth_client_secret = Column(String) config_google_oauth_client_secret = Column(String)
config_mature_content_tags = Column(String) config_mature_content_tags = Column(String)
@ -441,13 +449,14 @@ class Config:
self.config_use_goodreads = data.config_use_goodreads self.config_use_goodreads = data.config_use_goodreads
self.config_goodreads_api_key = data.config_goodreads_api_key self.config_goodreads_api_key = data.config_goodreads_api_key
self.config_goodreads_api_secret = data.config_goodreads_api_secret self.config_goodreads_api_secret = data.config_goodreads_api_secret
self.config_use_ldap = data.config_use_ldap self.config_login_type = data.config_login_type
# self.config_use_ldap = data.config_use_ldap
self.config_ldap_provider_url = data.config_ldap_provider_url self.config_ldap_provider_url = data.config_ldap_provider_url
self.config_ldap_dn = data.config_ldap_dn self.config_ldap_dn = data.config_ldap_dn
self.config_use_github_oauth = data.config_use_github_oauth # self.config_use_github_oauth = data.config_use_github_oauth
self.config_github_oauth_client_id = data.config_github_oauth_client_id self.config_github_oauth_client_id = data.config_github_oauth_client_id
self.config_github_oauth_client_secret = data.config_github_oauth_client_secret self.config_github_oauth_client_secret = data.config_github_oauth_client_secret
self.config_use_google_oauth = data.config_use_google_oauth # self.config_use_google_oauth = data.config_use_google_oauth
self.config_google_oauth_client_id = data.config_google_oauth_client_id self.config_google_oauth_client_id = data.config_google_oauth_client_id
self.config_google_oauth_client_secret = data.config_google_oauth_client_secret self.config_google_oauth_client_secret = data.config_google_oauth_client_secret
if data.config_mature_content_tags: if data.config_mature_content_tags:
@ -740,12 +749,14 @@ def migrate_Database():
conn.execute("ALTER TABLE Settings ADD column `config_calibre` String DEFAULT ''") conn.execute("ALTER TABLE Settings ADD column `config_calibre` String DEFAULT ''")
session.commit() session.commit()
try: try:
session.query(exists().where(Settings.config_use_ldap)).scalar() session.query(exists().where(Settings.config_login_type)).scalar()
except exc.OperationalError: except exc.OperationalError:
conn = engine.connect() conn = engine.connect()
conn.execute("ALTER TABLE Settings ADD column `config_use_ldap` INTEGER DEFAULT 0") conn.execute("ALTER TABLE Settings ADD column `config_login_type` INTEGER DEFAULT 0")
conn.execute("ALTER TABLE Settings ADD column `config_ldap_provider_url` String DEFAULT ''") conn.execute("ALTER TABLE Settings ADD column `config_ldap_provider_url` String DEFAULT ''")
conn.execute("ALTER TABLE Settings ADD column `config_ldap_dn` String DEFAULT ''") conn.execute("ALTER TABLE Settings ADD column `config_ldap_dn` String DEFAULT ''")
conn.execute("ALTER TABLE Settings ADD column `config_github_oauth_client_id` String DEFAULT ''")
conn.execute("ALTER TABLE Settings ADD column `config_github_oauth_client_secret` String DEFAULT ''")
session.commit() session.commit()
try: try:
session.query(exists().where(Settings.config_theme)).scalar() session.query(exists().where(Settings.config_theme)).scalar()
@ -760,22 +771,6 @@ def migrate_Database():
conn.execute("ALTER TABLE Settings ADD column `config_updatechannel` INTEGER DEFAULT 0") conn.execute("ALTER TABLE Settings ADD column `config_updatechannel` INTEGER DEFAULT 0")
session.commit() session.commit()
try:
session.query(exists().where(Settings.config_use_github_oauth)).scalar()
except exc.OperationalError:
conn = engine.connect()
conn.execute("ALTER TABLE Settings ADD column `config_use_github_oauth` INTEGER DEFAULT 0")
conn.execute("ALTER TABLE Settings ADD column `config_github_oauth_client_id` String DEFAULT ''")
conn.execute("ALTER TABLE Settings ADD column `config_github_oauth_client_secret` String DEFAULT ''")
session.commit()
try:
session.query(exists().where(Settings.config_use_google_oauth)).scalar()
except exc.OperationalError:
conn = engine.connect()
conn.execute("ALTER TABLE Settings ADD column `config_use_google_oauth` INTEGER DEFAULT 0")
conn.execute("ALTER TABLE Settings ADD column `config_google_oauth_client_id` String DEFAULT ''")
conn.execute("ALTER TABLE Settings ADD column `config_google_oauth_client_secret` String DEFAULT ''")
session.commit()
# Remove login capability of user Guest # Remove login capability of user Guest
conn = engine.connect() conn = engine.connect()
conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''") conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''")
@ -789,8 +784,8 @@ def clean_database():
#get LDAP connection #get LDAP connection
def get_ldap_connection(): def get_ldap_connection(ldap_provider_url):
conn = ldap.initialize('ldap://{}'.format(config.config_ldap_provider_url)) conn = ldap.initialize('ldap://{}'.format(ldap_provider_url))
return conn return conn

View File

@ -1325,10 +1325,10 @@ 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_use_ldap and user: if config.config_login_type == 1 and user:
import ldap
try: try:
ub.User.try_login(form['username'], form['password']) ub.User.try_login(form['username'], form['password'], config.config_ldap_dn,
config.config_ldap_provider_url)
login_user(user, remember=True) login_user(user, remember=True)
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")
return redirect_back(url_for("web.index")) return redirect_back(url_for("web.index"))
@ -1336,7 +1336,10 @@ def login():
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
app.logger.info('LDAP Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress) app.logger.info('LDAP Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress)
flash(_(u"Wrong Username or Password"), category="error") flash(_(u"Wrong Username or Password"), category="error")
el''' except ldap.SERVER_DOWN:
app.logger.info('LDAP Login failed, LDAP Server down')
flash(_(u"Could not login. LDAP server down, please contact your administrator"), category="error")
else:
if user and check_password_hash(user.password, form['password']) and user.nickname is not "Guest": if user and check_password_hash(user.password, form['password']) and user.nickname is not "Guest":
login_user(user, remember=True) login_user(user, remember=True)
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")