From 826893e42e9fe54d663b63508ff939aef610dbdd Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 21 Jan 2021 20:24:10 +0100 Subject: [PATCH 1/3] [Plugin] balance: Allow saving shortcuts --- flaschengeist/controller/userController.py | 6 ++++++ flaschengeist/plugins/balance/__init__.py | 24 ++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/flaschengeist/controller/userController.py b/flaschengeist/controller/userController.py index 3f0c9b0..024297d 100644 --- a/flaschengeist/controller/userController.py +++ b/flaschengeist/controller/userController.py @@ -199,3 +199,9 @@ def load_avatar(user: User): def save_avatar(user, avatar): current_app.config["FG_AUTH_BACKEND"].set_avatar(user, avatar) db.session.commit() + + +def persist(user=None): + if user: + db.session.add(user) + db.session.commit() diff --git a/flaschengeist/plugins/balance/__init__.py b/flaschengeist/plugins/balance/__init__.py index e56e372..6860a8e 100644 --- a/flaschengeist/plugins/balance/__init__.py +++ b/flaschengeist/plugins/balance/__init__.py @@ -4,6 +4,8 @@ Extends users plugin with balance functions """ from datetime import datetime, timezone + +from flaschengeist.utils.HTTP import no_content from flask import Blueprint, request, jsonify from werkzeug.exceptions import Forbidden, BadRequest @@ -39,31 +41,41 @@ class BalancePlugin(Plugin): db.create_all() -@balance_bp.route("/users//balance/shortcuts", methods=["GET"]) +@balance_bp.route("/users//balance/shortcuts", methods=["GET", "PUT"]) @login_required() def get_shortcuts(userid, current_session: Session): - """Get set limit of an user + """Get balance shortcuts of an user - Route: ``/users//balance/limit`` | Method: ``GET`` + Route: ``/users//balance/shortcuts`` | Method: ``GET`` or ``PUT`` + POST-data: On ``PUT`` json encoded array of floats Args: userid: Userid identifying the user current_session: Session sent with Authorization Header Returns: - JSON object containing the limit (or Null if no limit set) or HTTP error + GET: JSON object containing the shortcuts as float array or HTTP error + PUT: HTTP-created or HTTP error """ if userid != current_session._user.userid: raise Forbidden user = userController.get_user(userid) - return jsonify(user.get_attribute("balance_shortcuts", [])) + if request.method == "GET": + return jsonify(user.get_attribute("balance_shortcuts", [])) + else: + data = request.get_json() + if not isinstance(data, list) or all(isinstance(n, int) for n in data): + raise BadRequest + user.set_attribute("balance_shortcuts", data) + userController.persist() + return no_content() @balance_bp.route("/users//balance/limit", methods=["GET"]) @login_required() def get_limit(userid, current_session: Session): - """Get set limit of an user + """Get limit of an user Route: ``/users//balance/limit`` | Method: ``GET`` From c4b80f27ee62f0fb873f9ca7d916afb56448e4a5 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Fri, 22 Jan 2021 00:17:51 +0100 Subject: [PATCH 2/3] [Plugin] balance: Fixed shortcut data type check --- flaschengeist/plugins/balance/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flaschengeist/plugins/balance/__init__.py b/flaschengeist/plugins/balance/__init__.py index 6860a8e..23a44ff 100644 --- a/flaschengeist/plugins/balance/__init__.py +++ b/flaschengeist/plugins/balance/__init__.py @@ -65,7 +65,7 @@ def get_shortcuts(userid, current_session: Session): return jsonify(user.get_attribute("balance_shortcuts", [])) else: data = request.get_json() - if not isinstance(data, list) or all(isinstance(n, int) for n in data): + if not isinstance(data, list) or not all(isinstance(n, (int, float)) for n in data): raise BadRequest user.set_attribute("balance_shortcuts", data) userController.persist() From 125ba1be78a20c89c395a869c4b654f06b8bf531 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Fri, 22 Jan 2021 14:11:16 +0100 Subject: [PATCH 3/3] [Plugin] users: Fixed installation of permissions and added documentation. --- flaschengeist/controller/userController.py | 8 +++++--- flaschengeist/models/user.py | 1 + flaschengeist/plugins/users/__init__.py | 17 +++++++---------- flaschengeist/plugins/users/permissions.py | 13 +++++++++++++ 4 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 flaschengeist/plugins/users/permissions.py diff --git a/flaschengeist/controller/userController.py b/flaschengeist/controller/userController.py index 024297d..8c13782 100644 --- a/flaschengeist/controller/userController.py +++ b/flaschengeist/controller/userController.py @@ -50,7 +50,7 @@ def request_reset(user: User): text = str(config["MESSAGES"]["password_text"]).format( name=user.display_name, username=user.userid, - link=f'https://{config["FLASCHENGEIST"]["domain"]}/reset?token={reset.token}' + link=f'https://{config["FLASCHENGEIST"]["domain"]}/reset?token={reset.token}', ) messageController.send_message(messageController.Message(user, text, subject)) @@ -106,7 +106,9 @@ def modify_user(user, password, new_password=None): if new_password: logger.debug(f"Password changed for user {user.userid}") - subject = str(config["MESSAGES"]["password_changed_subject"]).format(name=user.display_name, username=user.userid) + subject = str(config["MESSAGES"]["password_changed_subject"]).format( + name=user.display_name, username=user.userid + ) text = str(config["MESSAGES"]["password_changed_text"]).format( name=user.display_name, username=user.userid, @@ -185,7 +187,7 @@ def register(data): text = str(config["MESSAGES"]["welcome_text"]).format( name=user.display_name, username=user.userid, - password_link=f'https://{config["FLASCHENGEIST"]["domain"]}/reset?token={reset.token}' + password_link=f'https://{config["FLASCHENGEIST"]["domain"]}/reset?token={reset.token}', ) messageController.send_message(messageController.Message(user, text, subject)) diff --git a/flaschengeist/models/user.py b/flaschengeist/models/user.py index 62940d8..f69ea1f 100644 --- a/flaschengeist/models/user.py +++ b/flaschengeist/models/user.py @@ -104,6 +104,7 @@ class _UserAttribute(db.Model, ModelSerializeMixin): class _PasswordReset(db.Model): """Table containing password reset requests""" + __tablename__ = "password_reset" _user_id: User = db.Column("user", db.Integer, db.ForeignKey("user.id"), primary_key=True) user: User = db.relationship("User", foreign_keys=[_user_id]) diff --git a/flaschengeist/plugins/users/__init__.py b/flaschengeist/plugins/users/__init__.py index f5ed33d..7bdbdec 100644 --- a/flaschengeist/plugins/users/__init__.py +++ b/flaschengeist/plugins/users/__init__.py @@ -8,6 +8,7 @@ from flaschengeist.config import config from flask import Blueprint, request, jsonify, make_response, Response from werkzeug.exceptions import BadRequest, Forbidden, MethodNotAllowed, NotFound +from . import permissions from flaschengeist import logger from flaschengeist.models.user import User, _Avatar from flaschengeist.plugins import Plugin @@ -17,15 +18,11 @@ from flaschengeist.utils.HTTP import created from flaschengeist.utils.datetime import from_iso_format users_bp = Blueprint("users", __name__) -_permission_edit = "users_edit_other" -_permission_set_roles = "users_set_roles" -_permission_delete = "users_delete_other" -_permission_register = "users_register" class UsersPlugin(Plugin): def __init__(self, cfg): - super().__init__(blueprint=users_bp, permissions=[_permission_edit, _permission_delete, _permission_set_roles]) + super().__init__(blueprint=users_bp, permissions=permissions.permissions) @users_bp.route("/users", methods=["POST"]) @@ -46,7 +43,7 @@ def register(): logger.debug("Config for Registration is set to >{}<".format(registration)) raise MethodNotAllowed if registration == "managed": - extract_session(_permission_register) + extract_session(permissions.REGISTER) data = request.get_json() if not data: @@ -114,7 +111,7 @@ def get_avatar(userid): @login_required() def set_avatar(userid, current_session): user = userController.get_user(userid) - if userid != current_session._user.userid and not current_session._user.has_permission(_permission_edit): + if userid != current_session._user.userid and not current_session._user.has_permission(permissions.EDIT): raise Forbidden file = request.files.get("file") @@ -129,7 +126,7 @@ def set_avatar(userid, current_session): @users_bp.route("/users/", methods=["DELETE"]) -@login_required(permission=_permission_delete) +@login_required(permission=permissions.DELETE) def delete_user(userid, current_session): """Delete user by userid @@ -175,7 +172,7 @@ def edit_user(userid, current_session): author = user if userid != current_session._user.userid: author = current_session._user - if not author.has_permission(_permission_edit): + if not author.has_permission(permissions.EDIT): raise Forbidden else: if "password" not in data: @@ -190,7 +187,7 @@ def edit_user(userid, current_session): if "roles" in data: roles = set(data["roles"]) - if not author.has_permission(_permission_set_roles): + if not author.has_permission(permissions.SET_ROLES): if len(roles) != len(user.roles) or set(user.roles) != roles: raise Forbidden else: diff --git a/flaschengeist/plugins/users/permissions.py b/flaschengeist/plugins/users/permissions.py new file mode 100644 index 0000000..2406b95 --- /dev/null +++ b/flaschengeist/plugins/users/permissions.py @@ -0,0 +1,13 @@ +EDIT = "users_edit_other" +"""Can edit other users""" + +SET_ROLES = "users_set_roles" +"""Can assign roles to users""" + +DELETE = "users_delete" +"""Can delete users""" + +REGISTER = "users_register" +"""Can register new users""" + +permissions = [value for key, value in globals().items() if not key.startswith("_")]