diff --git a/cps/admin.py b/cps/admin.py index d5be6839..6abd5675 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -39,7 +39,6 @@ from sqlalchemy.exc import IntegrityError from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders import helper from werkzeug.security import generate_password_hash -from oauth_bb import oauth_check try: from urllib.parse import quote from imp import reload @@ -59,6 +58,19 @@ try: except ImportError: 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 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") return render_title_template("config_edit.html", content=config, origin=origin, gdriveError=gdriveError, - goodreads=goodreads_support, title=_(u"Basic Configuration"), + gfeature_support=feature_support, title=_(u"Basic Configuration"), page="config") # always show google drive settings, but in case of error deny support 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") return render_title_template("config_edit.html", content=config, origin=origin, gdriveError=gdriveError, - goodreads=goodreads_support, title=_(u"Basic Configuration"), + feature_support=feature_support, title=_(u"Basic Configuration"), page="config") if "config_certfile" in to_save: if content.config_certfile != to_save["config_certfile"]: @@ -392,7 +404,7 @@ def configuration_helper(origin): content.config_ebookconverter = int(to_save["config_ebookconverter"]) #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: ub.session.commit() 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, title=_(u"Basic Configuration"), page="config") 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_dn = to_save["config_ldap_dn"] db_change = True diff --git a/cps/ldap.py b/cps/ldap.py deleted file mode 100644 index 93995b3e..00000000 --- a/cps/ldap.py +++ /dev/null @@ -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 . - -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 diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py index 830292e0..33e64a39 100644 --- a/cps/oauth_bb.py +++ b/cps/oauth_bb.py @@ -20,13 +20,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see -try: - from flask_dance.contrib.github import make_github_blueprint, github - from flask_dance.contrib.google import make_google_blueprint, google - from flask_dance.consumer import oauth_authorized, oauth_error - from oauth import OAuthBackend -except ImportError: - pass + +from flask_dance.contrib.github import make_github_blueprint, github +from flask_dance.contrib.google import make_google_blueprint, google +from flask_dance.consumer import oauth_authorized, oauth_error +from oauth import OAuthBackend from sqlalchemy.orm.exc import NoResultFound from flask import flash, session, redirect, url_for, request, make_response, abort import json diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 7cd07e18..62934259 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -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() { var updateTimerID; var updateText; @@ -179,7 +196,9 @@ $(function() { }); }); + // Init all data control handlers to default $("input[data-control]").trigger("change"); + $("select[data-control]").trigger("change"); $("#bookDetailsModal") .on("show.bs.modal", function(e) { diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 12f04e2d..af8412fb 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -176,53 +176,54 @@ {% endif %} - {% if feature_support['ldap'] %} -
- - -
-
-
- - -
-
- - -
-
- {% endif %} - {% if feature_support['oauth'] %} -
- - - {{_('Obtain GitHub OAuth Credentail')}} -
-
-
- - -
-
- - -
-
-
- - - {{_('Obtain Google OAuth Credentail')}} -
-
-
- - -
+ {% if feature_support['ldap'] or feature_support['oauth'] %}
- - + +
-
+ {% if feature_support['ldap'] %} +
+
+ + +
+
+ + +
+
+ {% endif %} + {% if feature_support['oauth'] %} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ {% endif %} {% endif %} diff --git a/cps/ub.py b/cps/ub.py index 494298ce..688d5bee 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -34,16 +34,15 @@ import cli try: from flask_dance.consumer.backend.sqla import OAuthConsumerMixin - import ldap oauth_support = True except ImportError: oauth_support = False pass -engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False) -Base = declarative_base() - -session = None +try: + import ldap +except ImportError: + pass ROLE_USER = 0 ROLE_ADMIN = 1 @@ -70,6 +69,16 @@ SIDEBAR_SORTED = 1024 MATURE_CONTENT = 2048 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" try: 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)') DEFAULT_PORT = 8083 -UPDATE_STABLE = 0 -AUTO_UPDATE_STABLE = 1 -UPDATE_NIGHTLY = 2 -AUTO_UPDATE_NIGHTLY = 4 +session = None + engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False) Base = declarative_base() @@ -188,12 +195,12 @@ class UserBase: def __repr__(self): return '' % self.nickname - #Login via LDAP method + # Login via LDAP method @staticmethod - def try_login(username, password): - conn = get_ldap_connection() + def try_login(username, password,config_dn, ldap_provider_url): + conn = get_ldap_connection(ldap_provider_url) conn.simple_bind_s( - config.config_ldap_dn.replace("%s", username), + config_dn.replace("%s", username), password) # 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_goodreads_api_key = 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_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_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_secret = Column(String) config_mature_content_tags = Column(String) @@ -441,13 +449,14 @@ class Config: self.config_use_goodreads = data.config_use_goodreads self.config_goodreads_api_key = data.config_goodreads_api_key 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_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_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_secret = data.config_google_oauth_client_secret if data.config_mature_content_tags: @@ -740,12 +749,14 @@ def migrate_Database(): conn.execute("ALTER TABLE Settings ADD column `config_calibre` String DEFAULT ''") session.commit() try: - session.query(exists().where(Settings.config_use_ldap)).scalar() + session.query(exists().where(Settings.config_login_type)).scalar() except exc.OperationalError: 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_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() try: 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") 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 conn = engine.connect() conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''") @@ -789,8 +784,8 @@ def clean_database(): #get LDAP connection -def get_ldap_connection(): - conn = ldap.initialize('ldap://{}'.format(config.config_ldap_provider_url)) +def get_ldap_connection(ldap_provider_url): + conn = ldap.initialize('ldap://{}'.format(ldap_provider_url)) return conn diff --git a/cps/web.py b/cps/web.py index ef87712f..b857d535 100644 --- a/cps/web.py +++ b/cps/web.py @@ -1325,10 +1325,10 @@ def login(): form = request.form.to_dict() user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == form['username'].strip().lower())\ .first() - '''if config.config_use_ldap and user: - import ldap + if config.config_login_type == 1 and user: 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) flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") return redirect_back(url_for("web.index")) @@ -1336,15 +1336,18 @@ def login(): 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") - el''' - if user and check_password_hash(user.password, form['password']) and user.nickname is not "Guest": - 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.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: - ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) - app.logger.info('Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress) - flash(_(u"Wrong Username or Password"), category="error") + if user and check_password_hash(user.password, form['password']) and user.nickname is not "Guest": + 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")) + else: + ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) + app.logger.info('Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress) + flash(_(u"Wrong Username or Password"), category="error") # next_url = request.args.get('next') # if next_url is None or not is_safe_url(next_url):