mirror of
				https://github.com/janeczku/calibre-web
				synced 2025-10-31 07:13:02 +00:00 
			
		
		
		
	refactoring to prevent web.py being the middle of the universe
This commit is contained in:
		| @@ -31,7 +31,7 @@ import werkzeug, flask, flask_login, flask_principal, jinja2 | |||||||
| from flask_babel import gettext as _ | from flask_babel import gettext as _ | ||||||
|  |  | ||||||
| from . import db, calibre_db, converter, uploader, server, isoLanguages, constants | from . import db, calibre_db, converter, uploader, server, isoLanguages, constants | ||||||
| from .web import render_title_template | from .render_template import render_title_template | ||||||
| try: | try: | ||||||
|     from flask_login import __version__ as flask_loginVersion |     from flask_login import __version__ as flask_loginVersion | ||||||
| except ImportError: | except ImportError: | ||||||
|   | |||||||
							
								
								
									
										163
									
								
								cps/admin.py
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								cps/admin.py
									
									
									
									
									
								
							| @@ -31,20 +31,25 @@ from datetime import datetime, timedelta | |||||||
|  |  | ||||||
| from babel import Locale as LC | from babel import Locale as LC | ||||||
| from babel.dates import format_datetime | from babel.dates import format_datetime | ||||||
| from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory | from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g | ||||||
| from flask_login import login_required, current_user, logout_user | from flask_login import login_required, current_user, logout_user, confirm_login | ||||||
| from flask_babel import gettext as _ | from flask_babel import gettext as _ | ||||||
| from sqlalchemy import and_ | from sqlalchemy import and_ | ||||||
| from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError | from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError | ||||||
| from sqlalchemy.sql.expression import func | from sqlalchemy.sql.expression import func, or_ | ||||||
|  |  | ||||||
| from . import constants, logger, helper, services | from . import constants, logger, helper, services | ||||||
| from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils | from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils | ||||||
| from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash | from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash | ||||||
| from .gdriveutils import is_gdrive_ready, gdrive_support | from .gdriveutils import is_gdrive_ready, gdrive_support | ||||||
| from .web import admin_required, render_title_template, before_request, unconfigured | from .render_template import render_title_template | ||||||
| from . import debug_info | from . import debug_info | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from functools import wraps | ||||||
|  | except ImportError: | ||||||
|  |     pass  # We're not using Python 3 | ||||||
|  |  | ||||||
| log = logger.create() | log = logger.create() | ||||||
|  |  | ||||||
| feature_support = { | feature_support = { | ||||||
| @@ -73,6 +78,49 @@ feature_support['gdrive'] = gdrive_support | |||||||
| admi = Blueprint('admin', __name__) | admi = Blueprint('admin', __name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def admin_required(f): | ||||||
|  |     """ | ||||||
|  |     Checks if current_user.role == 1 | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     @wraps(f) | ||||||
|  |     def inner(*args, **kwargs): | ||||||
|  |         if current_user.role_admin(): | ||||||
|  |             return f(*args, **kwargs) | ||||||
|  |         abort(403) | ||||||
|  |  | ||||||
|  |     return inner | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def unconfigured(f): | ||||||
|  |     """ | ||||||
|  |     Checks if calibre-web instance is not configured | ||||||
|  |     """ | ||||||
|  |     @wraps(f) | ||||||
|  |     def inner(*args, **kwargs): | ||||||
|  |         if not config.db_configured: | ||||||
|  |             return f(*args, **kwargs) | ||||||
|  |         abort(403) | ||||||
|  |  | ||||||
|  |     return inner | ||||||
|  |  | ||||||
|  | @admi.before_app_request | ||||||
|  | def before_request(): | ||||||
|  |     if current_user.is_authenticated: | ||||||
|  |         confirm_login() | ||||||
|  |     g.constants = constants | ||||||
|  |     g.user = current_user | ||||||
|  |     g.allow_registration = config.config_public_reg | ||||||
|  |     g.allow_anonymous = config.config_anonbrowse | ||||||
|  |     g.allow_upload = config.config_uploading | ||||||
|  |     g.current_theme = config.config_theme | ||||||
|  |     g.config_authors_max = config.config_authors_max | ||||||
|  |     g.shelves_access = ub.session.query(ub.Shelf).filter( | ||||||
|  |         or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all() | ||||||
|  |     if not config.db_configured and request.endpoint not in ( | ||||||
|  |         'admin.basic_configuration', 'login') and '/static/' not in request.path: | ||||||
|  |         return redirect(url_for('admin.basic_configuration')) | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/admin") | @admi.route("/admin") | ||||||
| @login_required | @login_required | ||||||
| @@ -1269,3 +1317,110 @@ def get_updater_status(): | |||||||
|         except Exception: |         except Exception: | ||||||
|             status['status'] = 11 |             status['status'] = 11 | ||||||
|     return json.dumps(status) |     return json.dumps(status) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @admi.route('/import_ldap_users') | ||||||
|  | @login_required | ||||||
|  | @admin_required | ||||||
|  | def import_ldap_users(): | ||||||
|  |     showtext = {} | ||||||
|  |     try: | ||||||
|  |         new_users = services.ldap.get_group_members(config.config_ldap_group_name) | ||||||
|  |     except (services.ldap.LDAPException, TypeError, AttributeError, KeyError) as e: | ||||||
|  |         log.exception(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) | ||||||
|  |  | ||||||
|  |     imported = 0 | ||||||
|  |     for username in new_users: | ||||||
|  |         user = username.decode('utf-8') | ||||||
|  |         if '=' in user: | ||||||
|  |             # if member object field is empty take user object as filter | ||||||
|  |             if config.config_ldap_member_user_object: | ||||||
|  |                 query_filter = config.config_ldap_member_user_object | ||||||
|  |             else: | ||||||
|  |                 query_filter = config.config_ldap_user_object | ||||||
|  |             try: | ||||||
|  |                 user_identifier = extract_user_identifier(user, query_filter) | ||||||
|  |             except Exception as e: | ||||||
|  |                 log.warning(e) | ||||||
|  |                 continue | ||||||
|  |         else: | ||||||
|  |             user_identifier = user | ||||||
|  |             query_filter = None | ||||||
|  |         try: | ||||||
|  |             user_data = services.ldap.get_object_details(user=user_identifier, query_filter=query_filter) | ||||||
|  |         except AttributeError as e: | ||||||
|  |             log.exception(e) | ||||||
|  |             continue | ||||||
|  |         if user_data: | ||||||
|  |             user_login_field = extract_dynamic_field_from_filter(user, config.config_ldap_user_object) | ||||||
|  |  | ||||||
|  |             username = user_data[user_login_field][0].decode('utf-8') | ||||||
|  |             # check for duplicate username | ||||||
|  |             if ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first(): | ||||||
|  |                 # if ub.session.query(ub.User).filter(ub.User.nickname == username).first(): | ||||||
|  |                 log.warning("LDAP User  %s Already in Database", user_data) | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             kindlemail = '' | ||||||
|  |             if 'mail' in user_data: | ||||||
|  |                 useremail = user_data['mail'][0].decode('utf-8') | ||||||
|  |                 if (len(user_data['mail']) > 1): | ||||||
|  |                     kindlemail = user_data['mail'][1].decode('utf-8') | ||||||
|  |  | ||||||
|  |             else: | ||||||
|  |                 log.debug('No Mail Field Found in LDAP Response') | ||||||
|  |                 useremail = username + '@email.com' | ||||||
|  |             # check for duplicate email | ||||||
|  |             if ub.session.query(ub.User).filter(func.lower(ub.User.email) == useremail.lower()).first(): | ||||||
|  |                 log.warning("LDAP Email %s Already in Database", user_data) | ||||||
|  |                 continue | ||||||
|  |             content = ub.User() | ||||||
|  |             content.nickname = username | ||||||
|  |             content.password = ''  # dummy password which will be replaced by ldap one | ||||||
|  |             content.email = useremail | ||||||
|  |             content.kindle_mail = kindlemail | ||||||
|  |             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() | ||||||
|  |                 imported +=1 | ||||||
|  |             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: | ||||||
|  |         showtext['text'] = _(u'{} User Successfully Imported'.format(imported)) | ||||||
|  |     return json.dumps(showtext) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def extract_user_data_from_field(user, field): | ||||||
|  |     match = re.search(field + "=([\d\s\w-]+)", user, re.IGNORECASE | re.UNICODE) | ||||||
|  |     if match: | ||||||
|  |         return match.group(1) | ||||||
|  |     else: | ||||||
|  |         raise Exception("Could Not Parse LDAP User: {}".format(user)) | ||||||
|  |  | ||||||
|  | def extract_dynamic_field_from_filter(user, filter): | ||||||
|  |     match = re.search("([a-zA-Z0-9-]+)=%s", filter, re.IGNORECASE | re.UNICODE) | ||||||
|  |     if match: | ||||||
|  |         return match.group(1) | ||||||
|  |     else: | ||||||
|  |         raise Exception("Could Not Parse LDAP Userfield: {}", user) | ||||||
|  |  | ||||||
|  | def extract_user_identifier(user, filter): | ||||||
|  |     dynamic_field = extract_dynamic_field_from_filter(user, filter) | ||||||
|  |     return extract_user_data_from_field(user, dynamic_field) | ||||||
|   | |||||||
| @@ -37,13 +37,38 @@ from . import config, get_locale, ub, db | |||||||
| from . import calibre_db | from . import calibre_db | ||||||
| from .services.worker import WorkerThread | from .services.worker import WorkerThread | ||||||
| from .tasks.upload import TaskUpload | from .tasks.upload import TaskUpload | ||||||
| from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required | from .render_template import render_title_template | ||||||
|  | from .usermanagement import login_required_if_no_ano | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from functools import wraps | ||||||
|  | except ImportError: | ||||||
|  |     pass  # We're not using Python 3 | ||||||
|  |  | ||||||
|  |  | ||||||
| editbook = Blueprint('editbook', __name__) | editbook = Blueprint('editbook', __name__) | ||||||
| log = logger.create() | log = logger.create() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def upload_required(f): | ||||||
|  |     @wraps(f) | ||||||
|  |     def inner(*args, **kwargs): | ||||||
|  |         if current_user.role_upload() or current_user.role_admin(): | ||||||
|  |             return f(*args, **kwargs) | ||||||
|  |         abort(403) | ||||||
|  |  | ||||||
|  |     return inner | ||||||
|  |  | ||||||
|  | def edit_required(f): | ||||||
|  |     @wraps(f) | ||||||
|  |     def inner(*args, **kwargs): | ||||||
|  |         if current_user.role_edit() or current_user.role_admin(): | ||||||
|  |             return f(*args, **kwargs) | ||||||
|  |         abort(403) | ||||||
|  |  | ||||||
|  |     return inner | ||||||
|  |  | ||||||
|  |  | ||||||
| # Modifies different Database objects, first check if elements have to be added to database, than check | # Modifies different Database objects, first check if elements have to be added to database, than check | ||||||
| # if elements have to be deleted, because they are no longer used | # if elements have to be deleted, because they are no longer used | ||||||
| def modify_database_object(input_elements, db_book_object, db_object, db_session, db_type): | def modify_database_object(input_elements, db_book_object, db_object, db_session, db_type): | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								cps/error_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								cps/error_handler.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | #  This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) | ||||||
|  | #    Copyright (C) 2018-2020 OzzieIsaacs | ||||||
|  | # | ||||||
|  | #  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 traceback | ||||||
|  | from flask import render_template | ||||||
|  | from werkzeug.exceptions import default_exceptions | ||||||
|  | try: | ||||||
|  |     from werkzeug.exceptions import FailedDependency | ||||||
|  | except ImportError: | ||||||
|  |     from werkzeug.exceptions import UnprocessableEntity as FailedDependency | ||||||
|  |  | ||||||
|  | from . import config, app, logger, services | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logger.create() | ||||||
|  |  | ||||||
|  | # custom error page | ||||||
|  | def error_http(error): | ||||||
|  |     return render_template('http_error.html', | ||||||
|  |                            error_code="Error {0}".format(error.code), | ||||||
|  |                            error_name=error.name, | ||||||
|  |                            issue=False, | ||||||
|  |                            instance=config.config_calibre_web_title | ||||||
|  |                            ), error.code | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def internal_error(error): | ||||||
|  |     return render_template('http_error.html', | ||||||
|  |                            error_code="Internal Server Error", | ||||||
|  |                            error_name=str(error), | ||||||
|  |                            issue=True, | ||||||
|  |                            error_stack=traceback.format_exc().split("\n"), | ||||||
|  |                            instance=config.config_calibre_web_title | ||||||
|  |                            ), 500 | ||||||
|  |  | ||||||
|  | # http error handling | ||||||
|  | for ex in default_exceptions: | ||||||
|  |     if ex < 500: | ||||||
|  |         app.register_error_handler(ex, error_http) | ||||||
|  |     elif ex == 500: | ||||||
|  |         app.register_error_handler(ex, internal_error) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if services.ldap: | ||||||
|  |     # Only way of catching the LDAPException upon logging in with LDAP server down | ||||||
|  |     @app.errorhandler(services.ldap.LDAPException) | ||||||
|  |     def handle_exception(e): | ||||||
|  |         log.debug('LDAP server not accessible while trying to login to opds feed') | ||||||
|  |         return error_http(FailedDependency()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # @app.errorhandler(InvalidRequestError) | ||||||
|  | #@app.errorhandler(OperationalError) | ||||||
|  | #def handle_db_exception(e): | ||||||
|  | #    db.session.rollback() | ||||||
|  | #    log.error('Database request error: %s',e) | ||||||
|  | #    return internal_error(InternalServerError(e)) | ||||||
| @@ -35,7 +35,7 @@ from flask_babel import gettext as _ | |||||||
| from flask_login import login_required | from flask_login import login_required | ||||||
|  |  | ||||||
| from . import logger, gdriveutils, config, ub, calibre_db | from . import logger, gdriveutils, config, ub, calibre_db | ||||||
| from .web import admin_required | from .admin import admin_required | ||||||
|  |  | ||||||
| gdrive = Blueprint('gdrive', __name__, url_prefix='/gdrive') | gdrive = Blueprint('gdrive', __name__, url_prefix='/gdrive') | ||||||
| log = logger.create() | log = logger.create() | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ from flask_babel import gettext as _ | |||||||
| from sqlalchemy.exc import OperationalError | from sqlalchemy.exc import OperationalError | ||||||
|  |  | ||||||
| from . import logger, ub, lm | from . import logger, ub, lm | ||||||
| from .web import render_title_template | from .render_template import render_title_template | ||||||
|  |  | ||||||
| try: | try: | ||||||
|     from functools import wraps |     from functools import wraps | ||||||
|   | |||||||
| @@ -30,12 +30,12 @@ from flask_babel import gettext as _ | |||||||
| from flask_dance.consumer import oauth_authorized, oauth_error | from flask_dance.consumer import oauth_authorized, oauth_error | ||||||
| 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_login import login_user, current_user | from flask_login import login_user, current_user, login_required | ||||||
| from sqlalchemy.orm.exc import NoResultFound | from sqlalchemy.orm.exc import NoResultFound | ||||||
| from sqlalchemy.exc import OperationalError | from sqlalchemy.exc import OperationalError | ||||||
|  |  | ||||||
| from . import constants, logger, config, app, ub | from . import constants, logger, config, app, ub | ||||||
| from .web import login_required |  | ||||||
| from .oauth import OAuthBackend, backend_resultcode | from .oauth import OAuthBackend, backend_resultcode | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,12 +28,13 @@ from functools import wraps | |||||||
| from flask import Blueprint, request, render_template, Response, g, make_response, abort | from flask import Blueprint, request, render_template, Response, g, make_response, abort | ||||||
| from flask_login import current_user | from flask_login import current_user | ||||||
| from sqlalchemy.sql.expression import func, text, or_, and_ | from sqlalchemy.sql.expression import func, text, or_, and_ | ||||||
| from werkzeug.security import check_password_hash |  | ||||||
|  |  | ||||||
| from . import constants, logger, config, db, calibre_db, ub, services, get_locale, isoLanguages | from . import constants, logger, config, db, calibre_db, ub, services, get_locale, isoLanguages | ||||||
| from .helper import get_download_link, get_book_cover | from .helper import get_download_link, get_book_cover | ||||||
| from .pagination import Pagination | from .pagination import Pagination | ||||||
| from .web import render_read_books, load_user_from_request | from .web import render_read_books | ||||||
|  | from .usermanagement import load_user_from_request | ||||||
| from flask_babel import gettext as _ | from flask_babel import gettext as _ | ||||||
| from babel import Locale as LC | from babel import Locale as LC | ||||||
| from babel.core import UnknownLocaleError | from babel.core import UnknownLocaleError | ||||||
|   | |||||||
							
								
								
									
										138
									
								
								cps/remotelogin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								cps/remotelogin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | #  This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) | ||||||
|  | #    Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11, | ||||||
|  | #                            andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh, | ||||||
|  | #                            falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe, | ||||||
|  | #                            ruben-herold, marblepebble, JackED42, SiphonSquirrel, | ||||||
|  | #                            apetresc, nanu-c, mutschler | ||||||
|  | # | ||||||
|  | #  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 json | ||||||
|  | from datetime import datetime | ||||||
|  |  | ||||||
|  | from flask import Blueprint, request, make_response, abort, url_for, flash, redirect | ||||||
|  | from flask_login import login_required, current_user, login_user | ||||||
|  | from flask_babel import gettext as _ | ||||||
|  |  | ||||||
|  | from . import config, logger, ub | ||||||
|  | from .render_template import render_title_template | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from functools import wraps | ||||||
|  | except ImportError: | ||||||
|  |     pass  # We're not using Python 3 | ||||||
|  |  | ||||||
|  | remotelogin = Blueprint('remotelogin', __name__) | ||||||
|  | log = logger.create() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def remote_login_required(f): | ||||||
|  |     @wraps(f) | ||||||
|  |     def inner(*args, **kwargs): | ||||||
|  |         if config.config_remote_login: | ||||||
|  |             return f(*args, **kwargs) | ||||||
|  |         if request.headers.get('X-Requested-With') == 'XMLHttpRequest': | ||||||
|  |             data = {'status': 'error', 'message': 'Forbidden'} | ||||||
|  |             response = make_response(json.dumps(data, ensure_ascii=False)) | ||||||
|  |             response.headers["Content-Type"] = "application/json; charset=utf-8" | ||||||
|  |             return response, 403 | ||||||
|  |         abort(403) | ||||||
|  |  | ||||||
|  |     return inner | ||||||
|  |  | ||||||
|  | @remotelogin.route('/remote/login') | ||||||
|  | @remote_login_required | ||||||
|  | def remote_login(): | ||||||
|  |     auth_token = ub.RemoteAuthToken() | ||||||
|  |     ub.session.add(auth_token) | ||||||
|  |     ub.session.commit() | ||||||
|  |  | ||||||
|  |     verify_url = url_for('web.verify_token', token=auth_token.auth_token, _external=true) | ||||||
|  |     log.debug(u"Remot Login request with token: %s", auth_token.auth_token) | ||||||
|  |     return render_title_template('remote_login.html', title=_(u"login"), token=auth_token.auth_token, | ||||||
|  |                                  verify_url=verify_url, page="remotelogin") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @remotelogin.route('/verify/<token>') | ||||||
|  | @remote_login_required | ||||||
|  | @login_required | ||||||
|  | def verify_token(token): | ||||||
|  |     auth_token = ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.auth_token == token).first() | ||||||
|  |  | ||||||
|  |     # Token not found | ||||||
|  |     if auth_token is None: | ||||||
|  |         flash(_(u"Token not found"), category="error") | ||||||
|  |         log.error(u"Remote Login token not found") | ||||||
|  |         return redirect(url_for('web.index')) | ||||||
|  |  | ||||||
|  |     # Token expired | ||||||
|  |     if datetime.now() > auth_token.expiration: | ||||||
|  |         ub.session.delete(auth_token) | ||||||
|  |         ub.session.commit() | ||||||
|  |  | ||||||
|  |         flash(_(u"Token has expired"), category="error") | ||||||
|  |         log.error(u"Remote Login token expired") | ||||||
|  |         return redirect(url_for('web.index')) | ||||||
|  |  | ||||||
|  |     # Update token with user information | ||||||
|  |     auth_token.user_id = current_user.id | ||||||
|  |     auth_token.verified = True | ||||||
|  |     ub.session.commit() | ||||||
|  |  | ||||||
|  |     flash(_(u"Success! Please return to your device"), category="success") | ||||||
|  |     log.debug(u"Remote Login token for userid %s verified", auth_token.user_id) | ||||||
|  |     return redirect(url_for('web.index')) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @remotelogin.route('/ajax/verify_token', methods=['POST']) | ||||||
|  | @remote_login_required | ||||||
|  | def token_verified(): | ||||||
|  |     token = request.form['token'] | ||||||
|  |     auth_token = ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.auth_token == token).first() | ||||||
|  |  | ||||||
|  |     data = {} | ||||||
|  |  | ||||||
|  |     # Token not found | ||||||
|  |     if auth_token is None: | ||||||
|  |         data['status'] = 'error' | ||||||
|  |         data['message'] = _(u"Token not found") | ||||||
|  |  | ||||||
|  |     # Token expired | ||||||
|  |     elif datetime.now() > auth_token.expiration: | ||||||
|  |         ub.session.delete(auth_token) | ||||||
|  |         ub.session.commit() | ||||||
|  |  | ||||||
|  |         data['status'] = 'error' | ||||||
|  |         data['message'] = _(u"Token has expired") | ||||||
|  |  | ||||||
|  |     elif not auth_token.verified: | ||||||
|  |         data['status'] = 'not_verified' | ||||||
|  |  | ||||||
|  |     else: | ||||||
|  |         user = ub.session.query(ub.User).filter(ub.User.id == auth_token.user_id).first() | ||||||
|  |         login_user(user) | ||||||
|  |  | ||||||
|  |         ub.session.delete(auth_token) | ||||||
|  |         ub.session.commit() | ||||||
|  |  | ||||||
|  |         data['status'] = 'success' | ||||||
|  |         log.debug(u"Remote Login for userid %s succeded", user.id) | ||||||
|  |         flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") | ||||||
|  |  | ||||||
|  |     response = make_response(json.dumps(data, ensure_ascii=False)) | ||||||
|  |     response.headers["Content-Type"] = "application/json; charset=utf-8" | ||||||
|  |  | ||||||
|  |     return response | ||||||
							
								
								
									
										99
									
								
								cps/render_template.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								cps/render_template.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | #  This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) | ||||||
|  | #    Copyright (C) 2018-2020 OzzieIsaacs | ||||||
|  | # | ||||||
|  | #  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/>. | ||||||
|  |  | ||||||
|  | from flask import render_template | ||||||
|  | from flask_babel import gettext as _ | ||||||
|  | from flask import g | ||||||
|  | from werkzeug.local import LocalProxy | ||||||
|  |  | ||||||
|  | from . import config, constants | ||||||
|  | from .ub import User | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _get_sidebar_config(kwargs=None): | ||||||
|  |     kwargs = kwargs or [] | ||||||
|  |     if 'content' in kwargs: | ||||||
|  |         content = kwargs['content'] | ||||||
|  |         content = isinstance(content, (User, LocalProxy)) and not content.role_anonymous() | ||||||
|  |     else: | ||||||
|  |         content = 'conf' in kwargs | ||||||
|  |     sidebar = list() | ||||||
|  |     sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new", | ||||||
|  |                     "visibility": constants.SIDEBAR_RECENT, 'public': True, "page": "root", | ||||||
|  |                     "show_text": _('Show recent books'), "config_show":False}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot", | ||||||
|  |                     "visibility": constants.SIDEBAR_HOT, 'public': True, "page": "hot", | ||||||
|  |                     "show_text": _('Show Hot Books'), "config_show": True}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.books_list', | ||||||
|  |                     "id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not g.user.is_anonymous), | ||||||
|  |                     "page": "download", "show_text": _('Show Downloaded Books'), | ||||||
|  |                     "config_show": content}) | ||||||
|  |     sidebar.append( | ||||||
|  |         {"glyph": "glyphicon-star", "text": _('Top Rated Books'), "link": 'web.books_list', "id": "rated", | ||||||
|  |          "visibility": constants.SIDEBAR_BEST_RATED, 'public': True, "page": "rated", | ||||||
|  |          "show_text": _('Show Top Rated Books'), "config_show": True}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read", | ||||||
|  |                     "visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), | ||||||
|  |                     "page": "read", "show_text": _('Show read and unread'), "config_show": content}) | ||||||
|  |     sidebar.append( | ||||||
|  |         {"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread", | ||||||
|  |          "visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread", | ||||||
|  |          "show_text": _('Show unread'), "config_show": False}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand", | ||||||
|  |                     "visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover", | ||||||
|  |                     "show_text": _('Show random books'), "config_show": True}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat", | ||||||
|  |                     "visibility": constants.SIDEBAR_CATEGORY, 'public': True, "page": "category", | ||||||
|  |                     "show_text": _('Show category selection'), "config_show": True}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-bookmark", "text": _('Series'), "link": 'web.series_list', "id": "serie", | ||||||
|  |                     "visibility": constants.SIDEBAR_SERIES, 'public': True, "page": "series", | ||||||
|  |                     "show_text": _('Show series selection'), "config_show": True}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-user", "text": _('Authors'), "link": 'web.author_list', "id": "author", | ||||||
|  |                     "visibility": constants.SIDEBAR_AUTHOR, 'public': True, "page": "author", | ||||||
|  |                     "show_text": _('Show author selection'), "config_show": True}) | ||||||
|  |     sidebar.append( | ||||||
|  |         {"glyph": "glyphicon-text-size", "text": _('Publishers'), "link": 'web.publisher_list', "id": "publisher", | ||||||
|  |          "visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher", | ||||||
|  |          "show_text": _('Show publisher selection'), "config_show":True}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang", | ||||||
|  |                     "visibility": constants.SIDEBAR_LANGUAGE, 'public': (g.user.filter_language() == 'all'), | ||||||
|  |                     "page": "language", | ||||||
|  |                     "show_text": _('Show language selection'), "config_show": True}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate", | ||||||
|  |                     "visibility": constants.SIDEBAR_RATING, 'public': True, | ||||||
|  |                     "page": "rating", "show_text": _('Show ratings selection'), "config_show": True}) | ||||||
|  |     sidebar.append({"glyph": "glyphicon-file", "text": _('File formats'), "link": 'web.formats_list', "id": "format", | ||||||
|  |                     "visibility": constants.SIDEBAR_FORMAT, 'public': True, | ||||||
|  |                     "page": "format", "show_text": _('Show file formats selection'), "config_show": True}) | ||||||
|  |     sidebar.append( | ||||||
|  |         {"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived", | ||||||
|  |          "visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived", | ||||||
|  |          "show_text": _('Show archived books'), "config_show": content}) | ||||||
|  |     sidebar.append( | ||||||
|  |         {"glyph": "glyphicon-th-list", "text": _('Books List'), "link": 'web.books_table', "id": "list", | ||||||
|  |          "visibility": constants.SIDEBAR_LIST, 'public': (not g.user.is_anonymous), "page": "list", | ||||||
|  |          "show_text": _('Show Books List'), "config_show": content}) | ||||||
|  |  | ||||||
|  |     return sidebar | ||||||
|  |  | ||||||
|  | # Returns the template for rendering and includes the instance name | ||||||
|  | def render_title_template(*args, **kwargs): | ||||||
|  |     sidebar = _get_sidebar_config(kwargs) | ||||||
|  |     return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, | ||||||
|  |                            accept=constants.EXTENSIONS_UPLOAD, | ||||||
|  |                            *args, **kwargs) | ||||||
| @@ -30,7 +30,8 @@ from sqlalchemy.sql.expression import func | |||||||
| from sqlalchemy.exc import OperationalError, InvalidRequestError | from sqlalchemy.exc import OperationalError, InvalidRequestError | ||||||
|  |  | ||||||
| from . import logger, ub, calibre_db | from . import logger, ub, calibre_db | ||||||
| from .web import login_required_if_no_ano, render_title_template | from .render_template import render_title_template | ||||||
|  | from .usermanagement import login_required_if_no_ano | ||||||
|  |  | ||||||
|  |  | ||||||
| shelf = Blueprint('shelf', __name__) | shelf = Blueprint('shelf', __name__) | ||||||
|   | |||||||
| @@ -2,18 +2,33 @@ | |||||||
| {% block body %} | {% block body %} | ||||||
| <div class="discover"> | <div class="discover"> | ||||||
|   <h2>{{title}}</h2> |   <h2>{{title}}</h2> | ||||||
|   {% if g.user.role_download() %} |   {% if g.user.role_download() %} | ||||||
|  <a id="shelf_down" href="{{ url_for('shelf.show_shelf', shelf_type=2, shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Download') }} </a> |   <a id="shelf_down" href="{{ url_for('shelf.show_shelf', shelf_type=2, shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Download') }} </a> | ||||||
|       {% endif %} |       {% endif %} | ||||||
|   {% if g.user.is_authenticated %} |   {% if g.user.is_authenticated %} | ||||||
|     {% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public  %} |     {% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public  %} | ||||||
|       <div id="delete_shelf" data-toggle="modal" data-target="#DeleteShelfDialog" class="btn btn-danger">{{ _('Delete this Shelf') }} </div> |       <div id="delete_shelf" data-toggle="modal" data-target="#DeleteShelfDialog" class="btn btn-danger">{{ _('Delete this Shelf') }} </div> | ||||||
|       <a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf') }} </a> |       <a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf') }} </a> | ||||||
|       <a id="order_shelf" href="{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Change order') }} </a> |       {% if entries.__len__() %} | ||||||
|  |         <a id="order_shelf" href="{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Change order') }} </a> | ||||||
|  |       {% endif %} | ||||||
|     {% endif %} |     {% endif %} | ||||||
|   {% endif %} |   {% endif %} | ||||||
|  |     <div class="filterheader hidden-xs hidden-sm"> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" id="auth_az" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authaz')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" id="auth_za" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authza')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a> | ||||||
|  |       {% if page == 'series' %} | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort ascending according to series index')}}" id="series_asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesasc')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a> | ||||||
|  |       <a data-toggle="tooltip" title="{{_('Sort descending according to series index')}}" id="series_desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesdesc')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a> | ||||||
|  |       {% endif %} | ||||||
|  |     </div> | ||||||
|   <div class="row display-flex"> |   <div class="row display-flex"> | ||||||
|  |  | ||||||
|     {% for entry in entries %} |     {% for entry in entries %} | ||||||
|     <div class="col-sm-3 col-lg-2 col-xs-6 book"> |     <div class="col-sm-3 col-lg-2 col-xs-6 book"> | ||||||
|       <div class="cover"> |       <div class="cover"> | ||||||
|   | |||||||
							
								
								
									
										71
									
								
								cps/ub.py
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								cps/ub.py
									
									
									
									
									
								
							| @@ -26,10 +26,8 @@ import uuid | |||||||
| from flask import session as flask_session | from flask import session as flask_session | ||||||
| from binascii import hexlify | from binascii import hexlify | ||||||
|  |  | ||||||
| from flask import g |  | ||||||
| from flask_babel import gettext as _ |  | ||||||
| from flask_login import AnonymousUserMixin, current_user | from flask_login import AnonymousUserMixin, current_user | ||||||
| from werkzeug.local import LocalProxy |  | ||||||
| try: | try: | ||||||
|     from flask_dance.consumer.backend.sqla import OAuthConsumerMixin |     from flask_dance.consumer.backend.sqla import OAuthConsumerMixin | ||||||
|     oauth_support = True |     oauth_support = True | ||||||
| @@ -57,73 +55,6 @@ Base = declarative_base() | |||||||
| searched_ids = {} | searched_ids = {} | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_sidebar_config(kwargs=None): |  | ||||||
|     kwargs = kwargs or [] |  | ||||||
|     if 'content' in kwargs: |  | ||||||
|         content = kwargs['content'] |  | ||||||
|         content = isinstance(content, (User, LocalProxy)) and not content.role_anonymous() |  | ||||||
|     else: |  | ||||||
|         content = 'conf' in kwargs |  | ||||||
|     sidebar = list() |  | ||||||
|     sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new", |  | ||||||
|                     "visibility": constants.SIDEBAR_RECENT, 'public': True, "page": "root", |  | ||||||
|                     "show_text": _('Show recent books'), "config_show":False}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot", |  | ||||||
|                     "visibility": constants.SIDEBAR_HOT, 'public': True, "page": "hot", |  | ||||||
|                     "show_text": _('Show Hot Books'), "config_show": True}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.books_list', |  | ||||||
|                     "id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not g.user.is_anonymous), |  | ||||||
|                     "page": "download", "show_text": _('Show Downloaded Books'), |  | ||||||
|                     "config_show": content}) |  | ||||||
|     sidebar.append( |  | ||||||
|         {"glyph": "glyphicon-star", "text": _('Top Rated Books'), "link": 'web.books_list', "id": "rated", |  | ||||||
|          "visibility": constants.SIDEBAR_BEST_RATED, 'public': True, "page": "rated", |  | ||||||
|          "show_text": _('Show Top Rated Books'), "config_show": True}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read", |  | ||||||
|                     "visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), |  | ||||||
|                     "page": "read", "show_text": _('Show read and unread'), "config_show": content}) |  | ||||||
|     sidebar.append( |  | ||||||
|         {"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread", |  | ||||||
|          "visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread", |  | ||||||
|          "show_text": _('Show unread'), "config_show": False}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand", |  | ||||||
|                     "visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover", |  | ||||||
|                     "show_text": _('Show random books'), "config_show": True}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat", |  | ||||||
|                     "visibility": constants.SIDEBAR_CATEGORY, 'public': True, "page": "category", |  | ||||||
|                     "show_text": _('Show category selection'), "config_show": True}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-bookmark", "text": _('Series'), "link": 'web.series_list', "id": "serie", |  | ||||||
|                     "visibility": constants.SIDEBAR_SERIES, 'public': True, "page": "series", |  | ||||||
|                     "show_text": _('Show series selection'), "config_show": True}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-user", "text": _('Authors'), "link": 'web.author_list', "id": "author", |  | ||||||
|                     "visibility": constants.SIDEBAR_AUTHOR, 'public': True, "page": "author", |  | ||||||
|                     "show_text": _('Show author selection'), "config_show": True}) |  | ||||||
|     sidebar.append( |  | ||||||
|         {"glyph": "glyphicon-text-size", "text": _('Publishers'), "link": 'web.publisher_list', "id": "publisher", |  | ||||||
|          "visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher", |  | ||||||
|          "show_text": _('Show publisher selection'), "config_show":True}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang", |  | ||||||
|                     "visibility": constants.SIDEBAR_LANGUAGE, 'public': (g.user.filter_language() == 'all'), |  | ||||||
|                     "page": "language", |  | ||||||
|                     "show_text": _('Show language selection'), "config_show": True}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate", |  | ||||||
|                     "visibility": constants.SIDEBAR_RATING, 'public': True, |  | ||||||
|                     "page": "rating", "show_text": _('Show ratings selection'), "config_show": True}) |  | ||||||
|     sidebar.append({"glyph": "glyphicon-file", "text": _('File formats'), "link": 'web.formats_list', "id": "format", |  | ||||||
|                     "visibility": constants.SIDEBAR_FORMAT, 'public': True, |  | ||||||
|                     "page": "format", "show_text": _('Show file formats selection'), "config_show": True}) |  | ||||||
|     sidebar.append( |  | ||||||
|         {"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived", |  | ||||||
|          "visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived", |  | ||||||
|          "show_text": _('Show archived books'), "config_show": content}) |  | ||||||
|     sidebar.append( |  | ||||||
|         {"glyph": "glyphicon-th-list", "text": _('Books List'), "link": 'web.books_table', "id": "list", |  | ||||||
|          "visibility": constants.SIDEBAR_LIST, 'public': (not g.user.is_anonymous), "page": "list", |  | ||||||
|          "show_text": _('Show Books List'), "config_show": content}) |  | ||||||
|  |  | ||||||
|     return sidebar |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def store_ids(result): | def store_ids(result): | ||||||
|     ids = list() |     ids = list() | ||||||
|     for element in result: |     for element in result: | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								cps/usermanagement.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								cps/usermanagement.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | #  This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) | ||||||
|  | #    Copyright (C) 2018-2020 OzzieIsaacs | ||||||
|  | # | ||||||
|  | #  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 base64 | ||||||
|  | import binascii | ||||||
|  |  | ||||||
|  | from sqlalchemy.sql.expression import func | ||||||
|  | from werkzeug.security import check_password_hash | ||||||
|  | from flask_login import login_required | ||||||
|  |  | ||||||
|  | from . import lm, ub, config, constants, services | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from functools import wraps | ||||||
|  | except ImportError: | ||||||
|  |     pass  # We're not using Python 3 | ||||||
|  |  | ||||||
|  | def login_required_if_no_ano(func): | ||||||
|  |     @wraps(func) | ||||||
|  |     def decorated_view(*args, **kwargs): | ||||||
|  |         if config.config_anonbrowse == 1: | ||||||
|  |             return func(*args, **kwargs) | ||||||
|  |         return login_required(func)(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     return decorated_view | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _fetch_user_by_name(username): | ||||||
|  |     return ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @lm.user_loader | ||||||
|  | def load_user(user_id): | ||||||
|  |     return ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @lm.request_loader | ||||||
|  | def load_user_from_request(request): | ||||||
|  |     if config.config_allow_reverse_proxy_header_login: | ||||||
|  |         rp_header_name = config.config_reverse_proxy_login_header_name | ||||||
|  |         if rp_header_name: | ||||||
|  |             rp_header_username = request.headers.get(rp_header_name) | ||||||
|  |             if rp_header_username: | ||||||
|  |                 user = _fetch_user_by_name(rp_header_username) | ||||||
|  |                 if user: | ||||||
|  |                     return user | ||||||
|  |  | ||||||
|  |     auth_header = request.headers.get("Authorization") | ||||||
|  |     if auth_header: | ||||||
|  |         user = load_user_from_auth_header(auth_header) | ||||||
|  |         if user: | ||||||
|  |             return user | ||||||
|  |  | ||||||
|  |     return | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def load_user_from_auth_header(header_val): | ||||||
|  |     if header_val.startswith('Basic '): | ||||||
|  |         header_val = header_val.replace('Basic ', '', 1) | ||||||
|  |     basic_username = basic_password = '' | ||||||
|  |     try: | ||||||
|  |         header_val = base64.b64decode(header_val).decode('utf-8') | ||||||
|  |         basic_username = header_val.split(':')[0] | ||||||
|  |         basic_password = header_val.split(':')[1] | ||||||
|  |     except (TypeError, UnicodeDecodeError, binascii.Error): | ||||||
|  |         pass | ||||||
|  |     user = _fetch_user_by_name(basic_username) | ||||||
|  |     if user and config.config_login_type == constants.LOGIN_LDAP and services.ldap: | ||||||
|  |         if services.ldap.bind_user(str(user.password), basic_password): | ||||||
|  |             return user | ||||||
|  |     if user and check_password_hash(str(user.password), basic_password): | ||||||
|  |         return user | ||||||
|  |     return | ||||||
							
								
								
									
										425
									
								
								cps/web.py
									
									
									
									
									
								
							
							
						
						
									
										425
									
								
								cps/web.py
									
									
									
									
									
								
							| @@ -22,47 +22,40 @@ | |||||||
|  |  | ||||||
| from __future__ import division, print_function, unicode_literals | from __future__ import division, print_function, unicode_literals | ||||||
| import os | import os | ||||||
| import base64 |  | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| import json | import json | ||||||
| import mimetypes | import mimetypes | ||||||
| import traceback |  | ||||||
| import binascii |  | ||||||
| import re |  | ||||||
| import chardet  # dependency of requests | import chardet  # dependency of requests | ||||||
|  |  | ||||||
| from babel.dates import format_date | from babel.dates import format_date | ||||||
| from babel import Locale as LC | from babel import Locale as LC | ||||||
| from babel.core import UnknownLocaleError | from babel.core import UnknownLocaleError | ||||||
| from flask import Blueprint, jsonify | from flask import Blueprint, jsonify | ||||||
| from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for | from flask import request, redirect, send_from_directory, make_response, flash, abort, url_for | ||||||
| from flask import session as flask_session, send_file | from flask import session as flask_session | ||||||
| from flask_babel import gettext as _ | from flask_babel import gettext as _ | ||||||
| from flask_login import login_user, logout_user, login_required, current_user, confirm_login | from flask_login import login_user, logout_user, login_required, current_user | ||||||
| from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError | from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError | ||||||
| from sqlalchemy.sql.expression import text, func, true, false, not_, and_, or_ | from sqlalchemy.sql.expression import text, func, false, not_, and_ | ||||||
| from sqlalchemy.orm.attributes import flag_modified | from sqlalchemy.orm.attributes import flag_modified | ||||||
| from werkzeug.exceptions import default_exceptions |  | ||||||
| from sqlalchemy.sql.functions import coalesce | from sqlalchemy.sql.functions import coalesce | ||||||
|  |  | ||||||
| from .services.worker import WorkerThread | from .services.worker import WorkerThread | ||||||
|  |  | ||||||
| try: |  | ||||||
|     from werkzeug.exceptions import FailedDependency |  | ||||||
| except ImportError: |  | ||||||
|     from werkzeug.exceptions import UnprocessableEntity as FailedDependency |  | ||||||
| from werkzeug.datastructures import Headers | from werkzeug.datastructures import Headers | ||||||
| from werkzeug.security import generate_password_hash, check_password_hash | from werkzeug.security import generate_password_hash, check_password_hash | ||||||
|  |  | ||||||
| from . import constants, logger, isoLanguages, services | from . import constants, logger, isoLanguages, services | ||||||
| from . import lm, babel, db, ub, config, get_locale, app | from . import babel, db, ub, config, get_locale, app | ||||||
| from . import calibre_db | from . import calibre_db, shelf | ||||||
| from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download | from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download | ||||||
| from .helper import check_valid_domain, render_task_status, \ | from .helper import check_valid_domain, render_task_status, \ | ||||||
|     get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \ |     get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \ | ||||||
|     send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password |     send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password | ||||||
| from .pagination import Pagination | from .pagination import Pagination | ||||||
| from .redirect import redirect_back | from .redirect import redirect_back | ||||||
|  | from .usermanagement import login_required_if_no_ano | ||||||
|  | from .render_template import render_title_template | ||||||
|  |  | ||||||
| feature_support = { | feature_support = { | ||||||
|     'ldap': bool(services.ldap), |     'ldap': bool(services.ldap), | ||||||
| @@ -72,7 +65,6 @@ feature_support = { | |||||||
|  |  | ||||||
| try: | try: | ||||||
|     from .oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status |     from .oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status | ||||||
|  |  | ||||||
|     feature_support['oauth'] = True |     feature_support['oauth'] = True | ||||||
| except ImportError: | except ImportError: | ||||||
|     feature_support['oauth'] = False |     feature_support['oauth'] = False | ||||||
| @@ -83,55 +75,12 @@ try: | |||||||
| except ImportError: | except ImportError: | ||||||
|     pass  # We're not using Python 3 |     pass  # We're not using Python 3 | ||||||
|  |  | ||||||
|  |  | ||||||
| try: | try: | ||||||
|     from natsort import natsorted as sort |     from natsort import natsorted as sort | ||||||
| except ImportError: | except ImportError: | ||||||
|     sort = sorted  # Just use regular sort then, may cause issues with badly named pages in cbz/cbr files |     sort = sorted  # Just use regular sort then, may cause issues with badly named pages in cbz/cbr files | ||||||
|  |  | ||||||
|  |  | ||||||
| # custom error page |  | ||||||
| def error_http(error): |  | ||||||
|     return render_template('http_error.html', |  | ||||||
|                            error_code="Error {0}".format(error.code), |  | ||||||
|                            error_name=error.name, |  | ||||||
|                            issue=False, |  | ||||||
|                            instance=config.config_calibre_web_title |  | ||||||
|                            ), error.code |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def internal_error(error): |  | ||||||
|     return render_template('http_error.html', |  | ||||||
|                            error_code="Internal Server Error", |  | ||||||
|                            error_name=str(error), |  | ||||||
|                            issue=True, |  | ||||||
|                            error_stack=traceback.format_exc().split("\n"), |  | ||||||
|                            instance=config.config_calibre_web_title |  | ||||||
|                            ), 500 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # http error handling |  | ||||||
| for ex in default_exceptions: |  | ||||||
|     if ex < 500: |  | ||||||
|         app.register_error_handler(ex, error_http) |  | ||||||
|     elif ex == 500: |  | ||||||
|         app.register_error_handler(ex, internal_error) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if feature_support['ldap']: |  | ||||||
|     # Only way of catching the LDAPException upon logging in with LDAP server down |  | ||||||
|     @app.errorhandler(services.ldap.LDAPException) |  | ||||||
|     def handle_exception(e): |  | ||||||
|         log.debug('LDAP server not accessible while trying to login to opds feed') |  | ||||||
|         return error_http(FailedDependency()) |  | ||||||
|  |  | ||||||
| # @app.errorhandler(InvalidRequestError) |  | ||||||
| #@app.errorhandler(OperationalError) |  | ||||||
| #def handle_db_exception(e): |  | ||||||
| #    db.session.rollback() |  | ||||||
| #    log.error('Database request error: %s',e) |  | ||||||
| #    return internal_error(InternalServerError(e)) |  | ||||||
|  |  | ||||||
| @app.after_request | @app.after_request | ||||||
| def add_security_headers(resp): | def add_security_headers(resp): | ||||||
|     # resp.headers['Content-Security-Policy']= "script-src 'self' https://www.googleapis.com https://api.douban.com https://comicvine.gamespot.com;" |     # resp.headers['Content-Security-Policy']= "script-src 'self' https://www.googleapis.com https://api.douban.com https://comicvine.gamespot.com;" | ||||||
| @@ -147,104 +96,6 @@ log = logger.create() | |||||||
|  |  | ||||||
|  |  | ||||||
| # ################################### Login logic and rights management ############################################### | # ################################### Login logic and rights management ############################################### | ||||||
| def _fetch_user_by_name(username): |  | ||||||
|     return ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @lm.user_loader |  | ||||||
| def load_user(user_id): |  | ||||||
|     return ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @lm.request_loader |  | ||||||
| def load_user_from_request(request): |  | ||||||
|     if config.config_allow_reverse_proxy_header_login: |  | ||||||
|         rp_header_name = config.config_reverse_proxy_login_header_name |  | ||||||
|         if rp_header_name: |  | ||||||
|             rp_header_username = request.headers.get(rp_header_name) |  | ||||||
|             if rp_header_username: |  | ||||||
|                 user = _fetch_user_by_name(rp_header_username) |  | ||||||
|                 if user: |  | ||||||
|                     return user |  | ||||||
|  |  | ||||||
|     auth_header = request.headers.get("Authorization") |  | ||||||
|     if auth_header: |  | ||||||
|         user = load_user_from_auth_header(auth_header) |  | ||||||
|         if user: |  | ||||||
|             return user |  | ||||||
|  |  | ||||||
|     return |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def load_user_from_auth_header(header_val): |  | ||||||
|     if header_val.startswith('Basic '): |  | ||||||
|         header_val = header_val.replace('Basic ', '', 1) |  | ||||||
|     basic_username = basic_password = '' |  | ||||||
|     try: |  | ||||||
|         header_val = base64.b64decode(header_val).decode('utf-8') |  | ||||||
|         basic_username = header_val.split(':')[0] |  | ||||||
|         basic_password = header_val.split(':')[1] |  | ||||||
|     except (TypeError, UnicodeDecodeError, binascii.Error): |  | ||||||
|         pass |  | ||||||
|     user = _fetch_user_by_name(basic_username) |  | ||||||
|     if user and config.config_login_type == constants.LOGIN_LDAP and services.ldap: |  | ||||||
|         if services.ldap.bind_user(str(user.password), basic_password): |  | ||||||
|             return user |  | ||||||
|     if user and check_password_hash(str(user.password), basic_password): |  | ||||||
|         return user |  | ||||||
|     return |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def login_required_if_no_ano(func): |  | ||||||
|     @wraps(func) |  | ||||||
|     def decorated_view(*args, **kwargs): |  | ||||||
|         if config.config_anonbrowse == 1: |  | ||||||
|             return func(*args, **kwargs) |  | ||||||
|         return login_required(func)(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     return decorated_view |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def remote_login_required(f): |  | ||||||
|     @wraps(f) |  | ||||||
|     def inner(*args, **kwargs): |  | ||||||
|         if config.config_remote_login: |  | ||||||
|             return f(*args, **kwargs) |  | ||||||
|         if request.headers.get('X-Requested-With') == 'XMLHttpRequest': |  | ||||||
|             data = {'status': 'error', 'message': 'Forbidden'} |  | ||||||
|             response = make_response(json.dumps(data, ensure_ascii=False)) |  | ||||||
|             response.headers["Content-Type"] = "application/json; charset=utf-8" |  | ||||||
|             return response, 403 |  | ||||||
|         abort(403) |  | ||||||
|  |  | ||||||
|     return inner |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def admin_required(f): |  | ||||||
|     """ |  | ||||||
|     Checks if current_user.role == 1 |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     @wraps(f) |  | ||||||
|     def inner(*args, **kwargs): |  | ||||||
|         if current_user.role_admin(): |  | ||||||
|             return f(*args, **kwargs) |  | ||||||
|         abort(403) |  | ||||||
|  |  | ||||||
|     return inner |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def unconfigured(f): |  | ||||||
|     """ |  | ||||||
|     Checks if calibre-web instance is not configured |  | ||||||
|     """ |  | ||||||
|     @wraps(f) |  | ||||||
|     def inner(*args, **kwargs): |  | ||||||
|         if not config.db_configured: |  | ||||||
|             return f(*args, **kwargs) |  | ||||||
|         abort(403) |  | ||||||
|  |  | ||||||
|     return inner |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def download_required(f): | def download_required(f): | ||||||
| @@ -266,154 +117,6 @@ def viewer_required(f): | |||||||
|  |  | ||||||
|     return inner |     return inner | ||||||
|  |  | ||||||
|  |  | ||||||
| def upload_required(f): |  | ||||||
|     @wraps(f) |  | ||||||
|     def inner(*args, **kwargs): |  | ||||||
|         if current_user.role_upload() or current_user.role_admin(): |  | ||||||
|             return f(*args, **kwargs) |  | ||||||
|         abort(403) |  | ||||||
|  |  | ||||||
|     return inner |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def edit_required(f): |  | ||||||
|     @wraps(f) |  | ||||||
|     def inner(*args, **kwargs): |  | ||||||
|         if current_user.role_edit() or current_user.role_admin(): |  | ||||||
|             return f(*args, **kwargs) |  | ||||||
|         abort(403) |  | ||||||
|  |  | ||||||
|     return inner |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # ################################### Helper functions ################################################################ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @web.before_app_request |  | ||||||
| def before_request(): |  | ||||||
|     if current_user.is_authenticated: |  | ||||||
|         confirm_login() |  | ||||||
|     g.constants = constants |  | ||||||
|     g.user = current_user |  | ||||||
|     g.allow_registration = config.config_public_reg |  | ||||||
|     g.allow_anonymous = config.config_anonbrowse |  | ||||||
|     g.allow_upload = config.config_uploading |  | ||||||
|     g.current_theme = config.config_theme |  | ||||||
|     g.config_authors_max = config.config_authors_max |  | ||||||
|     g.shelves_access = ub.session.query(ub.Shelf).filter( |  | ||||||
|         or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all() |  | ||||||
|     if not config.db_configured and request.endpoint not in ( |  | ||||||
|         'admin.basic_configuration', 'login', "admin.config_pathchooser") and '/static/' not in request.path: |  | ||||||
|         return redirect(url_for('admin.basic_configuration')) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route('/import_ldap_users') |  | ||||||
| @login_required |  | ||||||
| @admin_required |  | ||||||
| def import_ldap_users(): |  | ||||||
|     showtext = {} |  | ||||||
|     try: |  | ||||||
|         new_users = services.ldap.get_group_members(config.config_ldap_group_name) |  | ||||||
|     except (services.ldap.LDAPException, TypeError, AttributeError, KeyError) as e: |  | ||||||
|         log.debug_or_exception(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) |  | ||||||
|  |  | ||||||
|     imported = 0 |  | ||||||
|     for username in new_users: |  | ||||||
|         user = username.decode('utf-8') |  | ||||||
|         if '=' in user: |  | ||||||
|             # if member object field is empty take user object as filter |  | ||||||
|             if config.config_ldap_member_user_object: |  | ||||||
|                 query_filter = config.config_ldap_member_user_object |  | ||||||
|             else: |  | ||||||
|                 query_filter = config.config_ldap_user_object |  | ||||||
|             try: |  | ||||||
|                 user_identifier = extract_user_identifier(user, query_filter) |  | ||||||
|             except Exception as e: |  | ||||||
|                 log.warning(e) |  | ||||||
|                 continue |  | ||||||
|         else: |  | ||||||
|             user_identifier = user |  | ||||||
|             query_filter = None |  | ||||||
|         try: |  | ||||||
|             user_data = services.ldap.get_object_details(user=user_identifier, query_filter=query_filter) |  | ||||||
|         except AttributeError as e: |  | ||||||
|             log.debug_or_exception(e) |  | ||||||
|             continue |  | ||||||
|         if user_data: |  | ||||||
|             user_login_field = extract_dynamic_field_from_filter(user, config.config_ldap_user_object) |  | ||||||
|  |  | ||||||
|             username = user_data[user_login_field][0].decode('utf-8') |  | ||||||
|             # check for duplicate username |  | ||||||
|             if ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first(): |  | ||||||
|                 log.warning("LDAP User  %s Already in Database", user_data) |  | ||||||
|                 continue |  | ||||||
|  |  | ||||||
|             kindlemail = '' |  | ||||||
|             if 'mail' in user_data: |  | ||||||
|                 useremail = user_data['mail'][0].decode('utf-8') |  | ||||||
|                 if (len(user_data['mail']) > 1): |  | ||||||
|                     kindlemail = user_data['mail'][1].decode('utf-8') |  | ||||||
|  |  | ||||||
|             else: |  | ||||||
|                 log.debug('No Mail Field Found in LDAP Response') |  | ||||||
|                 useremail = username + '@email.com' |  | ||||||
|             # check for duplicate email |  | ||||||
|             if ub.session.query(ub.User).filter(func.lower(ub.User.email) == useremail.lower()).first(): |  | ||||||
|                 log.warning("LDAP Email %s Already in Database", user_data) |  | ||||||
|                 continue |  | ||||||
|             content = ub.User() |  | ||||||
|             content.nickname = username |  | ||||||
|             content.password = ''  # dummy password which will be replaced by ldap one |  | ||||||
|             content.email = useremail |  | ||||||
|             content.kindle_mail = kindlemail |  | ||||||
|             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() |  | ||||||
|                 imported +=1 |  | ||||||
|             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: |  | ||||||
|         showtext['text'] = _(u'{} User Successfully Imported'.format(imported)) |  | ||||||
|     return json.dumps(showtext) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def extract_user_data_from_field(user, field): |  | ||||||
|     match = re.search(field + "=([\d\s\w-]+)", user, re.IGNORECASE | re.UNICODE) |  | ||||||
|     if match: |  | ||||||
|         return match.group(1) |  | ||||||
|     else: |  | ||||||
|         raise Exception("Could Not Parse LDAP User: {}".format(user)) |  | ||||||
|  |  | ||||||
| def extract_dynamic_field_from_filter(user, filter): |  | ||||||
|     match = re.search("([a-zA-Z0-9-]+)=%s", filter, re.IGNORECASE | re.UNICODE) |  | ||||||
|     if match: |  | ||||||
|         return match.group(1) |  | ||||||
|     else: |  | ||||||
|         raise Exception("Could Not Parse LDAP Userfield: {}", user) |  | ||||||
|  |  | ||||||
| def extract_user_identifier(user, filter): |  | ||||||
|     dynamic_field = extract_dynamic_field_from_filter(user, filter) |  | ||||||
|     return extract_user_data_from_field(user, dynamic_field) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # ################################### data provider functions ######################################################### | # ################################### data provider functions ######################################################### | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -650,14 +353,6 @@ def get_matching_tags(): | |||||||
|     return json_dumps |     return json_dumps | ||||||
|  |  | ||||||
|  |  | ||||||
| # Returns the template for rendering and includes the instance name |  | ||||||
| def render_title_template(*args, **kwargs): |  | ||||||
|     sidebar = ub.get_sidebar_config(kwargs) |  | ||||||
|     return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, |  | ||||||
|                            accept=constants.EXTENSIONS_UPLOAD, |  | ||||||
|                            *args, **kwargs) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def render_books_list(data, sort, book_id, page): | def render_books_list(data, sort, book_id, page): | ||||||
|     order = [db.Books.timestamp.desc()] |     order = [db.Books.timestamp.desc()] | ||||||
|     if sort == 'stored': |     if sort == 'stored': | ||||||
| @@ -735,6 +430,8 @@ def render_books_list(data, sort, book_id, page): | |||||||
|         term = json.loads(flask_session['query']) |         term = json.loads(flask_session['query']) | ||||||
|         offset = int(int(config.config_books_per_page) * (page - 1)) |         offset = int(int(config.config_books_per_page) * (page - 1)) | ||||||
|         return render_adv_search_results(term, offset, order, config.config_books_per_page) |         return render_adv_search_results(term, offset, order, config.config_books_per_page) | ||||||
|  |     elif data == "shelf": | ||||||
|  |         return shelf.show_shelf(1, book_id) | ||||||
|     else: |     else: | ||||||
|         website = data or "newest" |         website = data or "newest" | ||||||
|         entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order) |         entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order) | ||||||
| @@ -1691,101 +1388,7 @@ def logout(): | |||||||
|     return redirect(url_for('web.login')) |     return redirect(url_for('web.login')) | ||||||
|  |  | ||||||
|  |  | ||||||
| @web.route('/remote/login') |  | ||||||
| @remote_login_required |  | ||||||
| def remote_login(): |  | ||||||
|     auth_token = ub.RemoteAuthToken() |  | ||||||
|     ub.session.add(auth_token) |  | ||||||
|     try: |  | ||||||
|         ub.session.commit() |  | ||||||
|     except OperationalError: |  | ||||||
|         ub.session.rollback() |  | ||||||
|  |  | ||||||
|     verify_url = url_for('web.verify_token', token=auth_token.auth_token, _external=true) |  | ||||||
|     log.debug(u"Remot Login request with token: %s", auth_token.auth_token) |  | ||||||
|     return render_title_template('remote_login.html', title=_(u"login"), token=auth_token.auth_token, |  | ||||||
|                                  verify_url=verify_url, page="remotelogin") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @web.route('/verify/<token>') |  | ||||||
| @remote_login_required |  | ||||||
| @login_required |  | ||||||
| def verify_token(token): |  | ||||||
|     auth_token = ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.auth_token == token).first() |  | ||||||
|  |  | ||||||
|     # Token not found |  | ||||||
|     if auth_token is None: |  | ||||||
|         flash(_(u"Token not found"), category="error") |  | ||||||
|         log.error(u"Remote Login token not found") |  | ||||||
|         return redirect(url_for('web.index')) |  | ||||||
|  |  | ||||||
|     # Token expired |  | ||||||
|     if datetime.now() > auth_token.expiration: |  | ||||||
|         ub.session.delete(auth_token) |  | ||||||
|         ub.session.commit() |  | ||||||
|  |  | ||||||
|         flash(_(u"Token has expired"), category="error") |  | ||||||
|         log.error(u"Remote Login token expired") |  | ||||||
|         return redirect(url_for('web.index')) |  | ||||||
|  |  | ||||||
|     # Update token with user information |  | ||||||
|     auth_token.user_id = current_user.id |  | ||||||
|     auth_token.verified = True |  | ||||||
|     try: |  | ||||||
|         ub.session.commit() |  | ||||||
|     except OperationalError: |  | ||||||
|         ub.session.rollback() |  | ||||||
|  |  | ||||||
|     flash(_(u"Success! Please return to your device"), category="success") |  | ||||||
|     log.debug(u"Remote Login token for userid %s verified", auth_token.user_id) |  | ||||||
|     return redirect(url_for('web.index')) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @web.route('/ajax/verify_token', methods=['POST']) |  | ||||||
| @remote_login_required |  | ||||||
| def token_verified(): |  | ||||||
|     token = request.form['token'] |  | ||||||
|     auth_token = ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.auth_token == token).first() |  | ||||||
|  |  | ||||||
|     data = {} |  | ||||||
|  |  | ||||||
|     # Token not found |  | ||||||
|     if auth_token is None: |  | ||||||
|         data['status'] = 'error' |  | ||||||
|         data['message'] = _(u"Token not found") |  | ||||||
|  |  | ||||||
|     # Token expired |  | ||||||
|     elif datetime.now() > auth_token.expiration: |  | ||||||
|         ub.session.delete(auth_token) |  | ||||||
|         try: |  | ||||||
|             ub.session.commit() |  | ||||||
|         except OperationalError: |  | ||||||
|             ub.session.rollback() |  | ||||||
|  |  | ||||||
|         data['status'] = 'error' |  | ||||||
|         data['message'] = _(u"Token has expired") |  | ||||||
|  |  | ||||||
|     elif not auth_token.verified: |  | ||||||
|         data['status'] = 'not_verified' |  | ||||||
|  |  | ||||||
|     else: |  | ||||||
|         user = ub.session.query(ub.User).filter(ub.User.id == auth_token.user_id).first() |  | ||||||
|         login_user(user) |  | ||||||
|  |  | ||||||
|         ub.session.delete(auth_token) |  | ||||||
|         try: |  | ||||||
|             ub.session.commit() |  | ||||||
|         except OperationalError: |  | ||||||
|             ub.session.rollback() |  | ||||||
|  |  | ||||||
|         data['status'] = 'success' |  | ||||||
|         log.debug(u"Remote Login for userid %s succeded", user.id) |  | ||||||
|         flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") |  | ||||||
|  |  | ||||||
|     response = make_response(json.dumps(data, ensure_ascii=False)) |  | ||||||
|     response.headers["Content-Type"] = "application/json; charset=utf-8" |  | ||||||
|  |  | ||||||
|     return response |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # ################################### Users own configuration ######################################################### | # ################################### Users own configuration ######################################################### | ||||||
| @@ -1926,14 +1529,6 @@ def read_book(book_id, book_format): | |||||||
|                 log.debug(u"Start comic reader for %d", book_id) |                 log.debug(u"Start comic reader for %d", book_id) | ||||||
|                 return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"), |                 return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"), | ||||||
|                                              extension=fileExt) |                                              extension=fileExt) | ||||||
|         # if feature_support['rar']: |  | ||||||
|         #    extensionList = ["cbr","cbt","cbz"] |  | ||||||
|         # else: |  | ||||||
|         #     extensionList = ["cbt","cbz"] |  | ||||||
|         # for fileext in extensionList: |  | ||||||
|         #     if book_format.lower() == fileext: |  | ||||||
|         #         return render_title_template('readcbr.html', comicfile=book_id, |  | ||||||
|         #         extension=fileext, title=_(u"Read a Book"), book=book) |  | ||||||
|         log.debug(u"Error opening eBook. File does not exist or file is not accessible") |         log.debug(u"Error opening eBook. File does not exist or file is not accessible") | ||||||
|         flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error") |         flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error") | ||||||
|         return redirect(url_for("web.index")) |         return redirect(url_for("web.index")) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Ozzieisaacs
					Ozzieisaacs