From a43021e87c6a2f33a49bec763df6059a9eb3f2a8 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Wed, 7 Apr 2021 17:46:17 +0200 Subject: [PATCH 1/6] Fix for #1938 --- cps/static/js/table.js | 2 +- cps/templates/user_table.html | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 96901d0b..332b7f54 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -618,7 +618,7 @@ function responseHandler(res) { } function singleUserFormatter(value, row) { - return '' + this.buttontext + '' + return '' + this.buttontext + '' } function checkboxFormatter(value, row, index){ diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index e7ddf156..a7bee4f6 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -111,13 +111,13 @@ {{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), false, true, 2) }} {{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Columns Values"), false, true, 3) }} {{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "download_role",_('Upload'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "upload_role", _('Download'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}} {{ user_checkbox_row("role", "passwd_role", _('Change Password'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}} - {{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "upload_role",_('Upload'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "download_role", _('Download'), visiblility, all_roles)}} {{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}} + {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}} {{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show series selection'), visiblility, sidebar_settings)}} From fac232229ecbd8a3fc92faeaabb8c49729809293 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Wed, 7 Apr 2021 17:49:19 +0200 Subject: [PATCH 2/6] Added missing ead/unread category in user list #1938 --- cps/templates/user_table.html | 1 + 1 file changed, 1 insertion(+) diff --git a/cps/templates/user_table.html b/cps/templates/user_table.html index a7bee4f6..b8a9d2c2 100644 --- a/cps/templates/user_table.html +++ b/cps/templates/user_table.html @@ -120,6 +120,7 @@ {{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelfs'), visiblility, all_roles)}} {{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show language selection'), visiblility, sidebar_settings)}} + {{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show read/unread selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show series selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_category", _('Show category selection'), visiblility, sidebar_settings)}} {{ user_checkbox_row("sidebar_view", "sidebar_random", _('Show random books'), visiblility, sidebar_settings)}} From 78071841cca9ef8eed7b60c23739b8a0a46e63c1 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Wed, 7 Apr 2021 18:19:48 +0200 Subject: [PATCH 3/6] After Deleting a book the list page is still displayed #1938 --- cps/editbooks.py | 4 ++-- cps/static/js/main.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index cb8388ef..931ad13e 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -229,14 +229,14 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session): @editbook.route("/ajax/delete/") @login_required def delete_book_from_details(book_id): - return Response(delete_book(book_id,"", True), mimetype='application/json') + return Response(delete_book(book_id, "", True), mimetype='application/json') @editbook.route("/delete/", defaults={'book_format': ""}) @editbook.route("/delete//") @login_required def delete_book_ajax(book_id, book_format): - return delete_book(book_id,book_format, False) + return delete_book(book_id, book_format, False) def delete_whole_book(book_id, book): diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 11ce6ed1..81308c64 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -142,10 +142,11 @@ $("#delete_confirm").click(function() { //get data-id attribute of the clicked element var deleteId = $(this).data("delete-id"); var bookFormat = $(this).data("delete-format"); + var ajaxResponse = $(this).data("ajax"); if (bookFormat) { window.location.href = getPath() + "/delete/" + deleteId + "/" + bookFormat; } else { - if ($(this).data("delete-format")) { + if (ajaxResponse) { path = getPath() + "/ajax/delete/" + deleteId; $.ajax({ method:"get", @@ -187,6 +188,7 @@ $("#deleteModal").on("show.bs.modal", function(e) { } $(e.currentTarget).find("#delete_confirm").data("delete-id", bookId); $(e.currentTarget).find("#delete_confirm").data("delete-format", bookfomat); + $(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax")); }); From 067fb1b0b727fdfe22dc6d0cc3bfd253847f5900 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Wed, 7 Apr 2021 18:47:48 +0200 Subject: [PATCH 4/6] Prevent delete Guest user and redirect to admin page after user delete --- cps/admin.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 1d4b5a84..966e01ff 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -1185,10 +1185,14 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): if to_save.get("delete"): if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, ub.User.id != content.id).count(): - ub.session.query(ub.User).filter(ub.User.id == content.id).delete() - ub.session_commit() - flash(_(u"User '%(nick)s' deleted", nick=content.name), category="success") - return redirect(url_for('admin.admin')) + if content.name != "Guest": + ub.session.query(ub.User).filter(ub.User.id == content.id).delete() + ub.session_commit() + flash(_(u"User '%(nick)s' deleted", nick=content.name), category="success") + return redirect(url_for('admin.admin')) + else: + flash(_(u"Can't delete Guest User"), category="error") + return redirect(url_for('admin.admin')) else: flash(_(u"No admin user remaining, can't delete user", nick=content.name), category="error") return redirect(url_for('admin.admin')) @@ -1255,6 +1259,7 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): except OperationalError: ub.session.rollback() flash(_(u"Settings DB is not Writeable"), category="error") + return "" @admi.route("/admin/user/new", methods=["GET", "POST"]) @@ -1350,7 +1355,9 @@ def edit_user(user_id): kobo_support = feature_support['kobo'] and config.config_kobo_sync if request.method == "POST": to_save = request.form.to_dict() - _handle_edit_user(to_save, content, languages, translations, kobo_support) + resp = _handle_edit_user(to_save, content, languages, translations, kobo_support) + if resp: + return resp return render_title_template("user_edit.html", translations=translations, languages=languages, From 7561eabe522903a5f5ea5e6b8aef0f417697dadd Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Wed, 7 Apr 2021 18:56:17 +0200 Subject: [PATCH 5/6] Implement Backend to deny editing Guest rights restriction #1938 --- cps/admin.py | 7 +++++++ cps/static/js/table.js | 1 + 2 files changed, 8 insertions(+) diff --git a/cps/admin.py b/cps/admin.py index 966e01ff..04ddbed1 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -335,6 +335,9 @@ def edit_list_user(param): elif param == 'kindle_mail': user.kindle_mail = valid_email(vals['value']) if vals['value'] else "" elif param == 'role': + if user.name == "Guest" and int(vals['field_index']) in \ + [constants.ROLE_ADMIN, constants.ROLE_PASSWD, constants.ROLE_EDIT_SHELFS]: + raise Exception(_("Guest can't have this role")) if vals['value'] == 'true': user.role |= int(vals['field_index']) else: @@ -345,6 +348,8 @@ def edit_list_user(param): return _(u"No admin user remaining, can't remove admin role", nick=user.name), 400 user.role &= ~int(vals['field_index']) elif param == 'sidebar_view': + if user.name == "Guest" and int(vals['field_index']) == constants.SIDEBAR_READ_AND_UNREAD: + raise Exception(_("Guest can't have this view")) if vals['value'] == 'true': user.sidebar_view |= int(vals['field_index']) else: @@ -358,6 +363,8 @@ def edit_list_user(param): elif param == 'denied_column_value': user.denied_column_value = vals['value'] elif param == 'locale': + if user.name == "Guest": + raise Exception(_("Guest's Locale is determined automatically and can't be set")) user.locale = vals['value'] elif param == 'default_language': user.default_language = vals['value'] diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 332b7f54..b9e6a202 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -459,6 +459,7 @@ $(function() { $("input[data-name='admin_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); $("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); $("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); + $("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); // ToDo: Disable delete }, From 2d73f541c0a4bd5c7d5e95459e1a07a25d6b21ed Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Thu, 8 Apr 2021 19:37:08 +0200 Subject: [PATCH 6/6] Bugfix sort books list and user list Prevent transferring password hash to client --- cps/admin.py | 13 ++++++++++--- cps/config_sql.py | 2 +- cps/db.py | 4 ++-- cps/gdriveutils.py | 2 +- cps/static/js/table.js | 14 +++++++++----- cps/ub.py | 2 +- cps/web.py | 9 +++++---- 7 files changed, 29 insertions(+), 17 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 04ddbed1..4038977e 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -37,7 +37,7 @@ from flask_babel import gettext as _ from sqlalchemy import and_ from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError -from sqlalchemy.sql.expression import func, or_ +from sqlalchemy.sql.expression import func, or_, text from . import constants, logger, helper, services from .cli import filepicker @@ -244,6 +244,13 @@ def list_users(): off = request.args.get("offset") or 0 limit = request.args.get("limit") or 10 search = request.args.get("search") + sort = request.args.get("sort") + order = request.args.get("order") + if sort and order: + order = text(sort + " " + order) + else: + order = ub.User.name.desc() + all_user = ub.session.query(ub.User) if not config.config_anonbrowse: all_user = all_user.filter(ub.User.role.op('&')(constants.ROLE_ANONYMOUS) != constants.ROLE_ANONYMOUS) @@ -252,10 +259,10 @@ def list_users(): users = all_user.filter(or_(func.lower(ub.User.name).ilike("%" + search + "%"), func.lower(ub.User.kindle_mail).ilike("%" + search + "%"), func.lower(ub.User.email).ilike("%" + search + "%")))\ - .offset(off).limit(limit).all() + .order_by(order).offset(off).limit(limit).all() filtered_count = len(users) else: - users = all_user.offset(off).limit(limit).all() + users = all_user.order_by(order).offset(off).limit(limit).all() filtered_count = total_count for user in users: diff --git a/cps/config_sql.py b/cps/config_sql.py index 2ab0e3d6..3e5e4c59 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -24,7 +24,7 @@ import sys from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean, BLOB, JSON from sqlalchemy.exc import OperationalError try: - # Compability with sqlalchemy 2.0 + # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base except ImportError: from sqlalchemy.ext.declarative import declarative_base diff --git a/cps/db.py b/cps/db.py index 5cb04ed3..b875ded7 100644 --- a/cps/db.py +++ b/cps/db.py @@ -33,7 +33,7 @@ from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.exc import OperationalError try: - # Compability with sqlalchemy 2.0 + # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base except ImportError: from sqlalchemy.ext.declarative import declarative_base @@ -393,7 +393,7 @@ class AlchemyEncoder(json.JSONEncoder): if isinstance(o.__class__, DeclarativeMeta): # an SQLAlchemy class fields = {} - for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata']: + for field in [x for x in dir(o) if not x.startswith('_') and x != 'metadata' and x!="password"]: if field == 'books': continue data = o.__getattribute__(field) diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index a98d0b66..4c262661 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -29,7 +29,7 @@ from sqlalchemy import Column, UniqueConstraint from sqlalchemy import String, Integer from sqlalchemy.orm import sessionmaker, scoped_session try: - # Compability with sqlalchemy 2.0 + # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base except ImportError: from sqlalchemy.ext.declarative import declarative_base diff --git a/cps/static/js/table.js b/cps/static/js/table.js index b9e6a202..a0503976 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -460,8 +460,7 @@ $(function() { $("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); $("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); $("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true); - // ToDo: Disable delete - + $(".user-remove[data-pk='"+guest.data("pk")+"']").prop("disabled", true); }, // eslint-disable-next-line no-unused-vars @@ -604,7 +603,7 @@ function EbookActions (value, row) { /* Function for deleting books */ function UserActions (value, row) { return [ - "
", + "
", "", "
" ].join(""); @@ -624,9 +623,9 @@ function singleUserFormatter(value, row) { function checkboxFormatter(value, row, index){ if(value & this.column) - return ''; + return ''; else - return ''; + return ''; } function checkboxChange(checkbox, userId, field, field_index) { @@ -733,6 +732,11 @@ function user_handle (userId) { }); } +function checkboxSorter(a, b, c, d) +{ + return a - b +} + function test(){ console.log("hello"); } diff --git a/cps/ub.py b/cps/ub.py index 6cbc0383..a85f7404 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -44,7 +44,7 @@ from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.sql.expression import func try: - # Compability with sqlalchemy 2.0 + # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base except ImportError: from sqlalchemy.ext.declarative import declarative_base diff --git a/cps/web.py b/cps/web.py index e1acdcef..658ff735 100644 --- a/cps/web.py +++ b/cps/web.py @@ -755,11 +755,12 @@ def books_table(): def list_books(): off = request.args.get("offset") or 0 limit = request.args.get("limit") or config.config_books_per_page - # sort = request.args.get("sort") - if request.args.get("order") == 'desc': - order = [db.Books.timestamp.desc()] + sort = request.args.get("sort") + order = request.args.get("order") + if sort and order: + order = [text(sort + " " + order)] else: - order = [db.Books.timestamp.asc()] + order = [db.Books.timestamp.desc()] search = request.args.get("search") total_count = calibre_db.session.query(db.Books).count() if search: