From d3a2b40834d29afcb0b557f6ee1df97babf942c9 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Fri, 23 Oct 2020 02:29:55 +0200 Subject: [PATCH] Added some permissions, reworked permission system. --- flaschengeist/modules/__init__.py | 20 ++++++---- flaschengeist/modules/roles/__init__.py | 23 +++++------ flaschengeist/modules/schedule/__init__.py | 40 ++++++++++++------- flaschengeist/modules/users/__init__.py | 18 ++++++--- .../system/controller/roleController.py | 14 ++++--- .../system/controller/userController.py | 12 +++++- flaschengeist/system/decorator.py | 6 ++- flaschengeist/system/models/event.py | 8 +++- 8 files changed, 92 insertions(+), 49 deletions(-) diff --git a/flaschengeist/modules/__init__.py b/flaschengeist/modules/__init__.py index 9b085fb..e7faac1 100644 --- a/flaschengeist/modules/__init__.py +++ b/flaschengeist/modules/__init__.py @@ -18,10 +18,7 @@ class Plugin: pass def serialize(self): - return { - "version": self.version, - "permissions": self.permissions - } + return {"version": self.version, "permissions": self.permissions} class AuthPlugin(Plugin): @@ -38,9 +35,9 @@ class AuthPlugin(Plugin): def update_user(self, user): """If backend is using external data, then update this user instance with external data - - Args: - user: User object + ) + Args: + user: User object """ pass @@ -57,3 +54,12 @@ class AuthPlugin(Plugin): Error: Other errors if backend went mad (are not handled and will result in a 500 error) """ raise NotImplemented + + def delete_user(self, user): + """If backend is using (writeable) external data, then delete the user from external database. + + Args: + user: User object + + """ + pass diff --git a/flaschengeist/modules/roles/__init__.py b/flaschengeist/modules/roles/__init__.py index ba332e7..ece1db9 100644 --- a/flaschengeist/modules/roles/__init__.py +++ b/flaschengeist/modules/roles/__init__.py @@ -6,12 +6,13 @@ from flaschengeist.system.decorator import login_required from flaschengeist.system.controller import roleController roles_bp = Blueprint("roles", __name__) -roles_permission = "roles_edit" +_permission_edit = "roles_edit" +_permission_delete = "roles_delete" class RolesPlugin(Plugin): def __init__(self, config): - super().__init__(config, roles_bp, permissions=[roles_permission]) + super().__init__(config, roles_bp, permissions=[_permission_edit, _permission_delete]) ###################################################### @@ -27,7 +28,7 @@ class RolesPlugin(Plugin): @roles_bp.route("/roles", methods=["POST"]) -@login_required(permissions=[roles_permission]) +@login_required(permission=_permission_edit) def add_role(**kwargs): data = request.get_json() if not data or "name" not in data: @@ -41,7 +42,7 @@ def add_role(**kwargs): @roles_bp.route("/roles", methods=["GET"]) @login_required() def list_roles(**kwargs): - roles = roleController.get_roles() + roles = roleController.get_all() return jsonify(roles) @@ -55,18 +56,16 @@ def list_permissions(**kwargs): @roles_bp.route("/roles/", methods=["GET"]) @login_required() def __get_role(rid, **kwargs): - role = roleController.get_role(rid) + role = roleController.get(rid) if role: return jsonify({"id": role.id, "name": role, "permissions": role.permissions}) raise NotFound @roles_bp.route("/roles/", methods=["PUT"]) -@login_required(permissions=[roles_permission]) +@login_required(permission=_permission_edit) def __edit_role(rid, **kwargs): - role = roleController.get_role(rid) - if not role: - raise NotFound + role = roleController.get(rid) data = request.get_json() if "name" in data: @@ -78,9 +77,9 @@ def __edit_role(rid, **kwargs): @roles_bp.route("/roles/", methods=["DELETE"]) -@login_required(permissions=[roles_permission]) +@login_required(permission=_permission_delete) def __delete_role(rid, **kwargs): - if not roleController.delete_role(rid): - raise NotFound + role = roleController.get(rid) + roleController.delete(role) return jsonify({"ok": "ok"}) diff --git a/flaschengeist/modules/schedule/__init__.py b/flaschengeist/modules/schedule/__init__.py index 70b5a2b..3fceeff 100644 --- a/flaschengeist/modules/schedule/__init__.py +++ b/flaschengeist/modules/schedule/__init__.py @@ -10,13 +10,25 @@ from flaschengeist.system.decorator import login_required from flaschengeist.system.controller import eventController schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule") -schedule_perms = {"EDIT_EVENT": "schedule_edit_event", - "NEW_EVENT": "schedule_create_event"} +_permission_edit_type = "schedule_edit_type" +_permission_edit = "schedule_edit" +_permission_create = "schedule_create" +_permission_delete = "schedule_delete" +_permission_assign = "schedule_assign_other" class SchedulePlugin(Plugin): def __init__(self, config): - super().__init__(blueprint=schedule_bp, permissions=schedule_perms.values()) + super().__init__( + blueprint=schedule_bp, + permissions=[ + _permission_create, + _permission_edit, + _permission_edit_type, + _permission_delete, + _permission_assign, + ], + ) #################################################################################### @@ -41,10 +53,10 @@ class SchedulePlugin(Plugin): #################################################################################### -@schedule_bp.route("/events/", methods=["GET"]) +@schedule_bp.route("/events/", methods=["GET"]) @login_required() -def __get_event(id, **kwargs): - event = eventController.get_event(id) +def __get_event(eid, **kwargs): + event = eventController.get_event(eid) if not event: raise NotFound return jsonify(event) @@ -86,7 +98,7 @@ def __get_events(year=datetime.now().year, month=datetime.now().month, day=None, @schedule_bp.route("/eventKinds", methods=["POST"]) -@login_required() +@login_required(permission=_permission_edit_type) def __new_event_kind(**kwargs): data = request.get_json() if "name" not in data: @@ -96,7 +108,7 @@ def __new_event_kind(**kwargs): @schedule_bp.route("/slotKinds", methods=["POST"]) -@login_required() +@login_required(permission=_permission_edit_type) def __new_slot_kind(**kwargs): data = request.get_json() if not data or "name" not in data: @@ -106,7 +118,7 @@ def __new_slot_kind(**kwargs): @schedule_bp.route("/events", methods=["POST"]) -@login_required(permissions=[schedule_perms["NEW_EVENT"]]) +@login_required(permission=_permission_create) def __new_event(**kwargs): data = request.get_json() event = eventController.create_event( @@ -119,7 +131,7 @@ def __new_event(**kwargs): @schedule_bp.route("/events/", methods=["DELETE"]) -@login_required(permissions=[schedule_perms["EDIT_EVENT"]]) +@login_required(permission=_permission_delete) def __delete_event(event_id, **kwargs): if not eventController.delete_event(event_id): raise NotFound @@ -128,7 +140,7 @@ def __delete_event(event_id, **kwargs): @schedule_bp.route("/eventKinds/", methods=["PUT"]) -@login_required() +@login_required(permission=_permission_edit_type) def __edit_event_kind(event_id, **kwargs): data = request.get_json() if not data or "name" not in data: @@ -156,7 +168,7 @@ def __get_slot(event_id, slot_id, **kwargs): @schedule_bp.route("/events//slots/", methods=["DELETE"]) -@login_required() +@login_required(permission=_permission_delete) def __delete_slot(event_id, slot_id, **kwargs): if eventController.delete_event_slot(slot_id, event_id): return jsonify({"ok": "ok"}) @@ -164,7 +176,7 @@ def __delete_slot(event_id, slot_id, **kwargs): @schedule_bp.route("/events//slots/", methods=["PUT"]) -@login_required() +@login_required(permission=_permission_edit) def __update_slot(event_id, slot_id, **kwargs): data = request.get_json() if not data: @@ -178,7 +190,7 @@ def __update_slot(event_id, slot_id, **kwargs): @schedule_bp.route("/events//slots", methods=["POST"]) -@login_required() +@login_required(permission=_permission_edit) def __add_slot(event_id, **kwargs): event = eventController.get_event(event_id) if not event: diff --git a/flaschengeist/modules/users/__init__.py b/flaschengeist/modules/users/__init__.py index fd2d11c..bba35f3 100644 --- a/flaschengeist/modules/users/__init__.py +++ b/flaschengeist/modules/users/__init__.py @@ -7,12 +7,13 @@ from flaschengeist.system.decorator import login_required from flaschengeist.system.controller import userController users_bp = Blueprint("users", __name__) -users_perm = "users_edit_other" +_permission_edit = "users_edit_other" +_permission_delete = "users_delete_other" class UsersPlugin(Plugin): def __init__(self, config): - super().__init__(blueprint=users_bp, permissions=[users_perm]) + super().__init__(blueprint=users_bp, permissions=[_permission_edit, _permission_delete]) ################################################# # Routes # @@ -49,15 +50,22 @@ def __get_user(uid, **kwargs): raise NotFound +@users_bp.route("/users/", methods=["DELETE"]) +@login_required(permission=_permission_delete) +def __delete_user(uid, **kwargs): + logger.debug("Delete user {{ {} }}".format(uid)) + user = userController.get_user(uid) + userController.delete(user) + return jsonify({"ok": "ok"}) + + @users_bp.route("/users/", methods=["PUT"]) @login_required() def __edit_user(uid, **kwargs): logger.debug("Modify information of user {{ {} }}".format(uid)) user = userController.get_user(uid) - if not user: - raise NotFound - if uid != kwargs["access_token"].user.userid and user.has_permissions([users_perm]): + if uid != kwargs["access_token"].user.userid and user.has_permission(_permission_edit): return Forbidden data = request.get_json() diff --git a/flaschengeist/system/controller/roleController.py b/flaschengeist/system/controller/roleController.py index 71baa80..0758c53 100644 --- a/flaschengeist/system/controller/roleController.py +++ b/flaschengeist/system/controller/roleController.py @@ -1,17 +1,19 @@ from sqlalchemy.exc import IntegrityError -from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequest, NotFound from flaschengeist.system.models.user import Role, Permission from flaschengeist.system.database import db from flaschengeist import logger -def get_roles(): +def get_all(): return Role.query.all() -def get_role(rid): - return Role.query.get(rid) +def get(rid): + role = Role.query.get(rid).one_or_none() + if not role: + raise NotFound def get_permissions(): @@ -47,9 +49,9 @@ def create_role(name, permissions=[]): return role.id -def delete_role(id): +def delete(role): try: - num = Role.query.filter(Role.id == id).delete() + num = Role.query.filter(Role.id == role.id).delete() except IntegrityError: logger.debug("IntegrityError: Role might still be in use", exc_info=True) raise BadRequest("Role still in use") diff --git a/flaschengeist/system/controller/userController.py b/flaschengeist/system/controller/userController.py index 1e33f24..4871fc7 100644 --- a/flaschengeist/system/controller/userController.py +++ b/flaschengeist/system/controller/userController.py @@ -1,4 +1,5 @@ from flask import current_app +from werkzeug.exceptions import NotFound from flaschengeist.system.models.user import User, Role from flaschengeist.system.database import db @@ -51,4 +52,13 @@ def get_user_by_role(role: Role): def get_user(uid): - return User.query.filter(User.userid == uid).one_or_none() + user = User.query.filter(User.userid == uid).one_or_none() + if not user: + raise NotFound + return user + + +def delete(user): + current_app.config["FG_AUTH_BACKEND"].delete_user(user) + db.session.delete(user) + db.session.commit() diff --git a/flaschengeist/system/decorator.py b/flaschengeist/system/decorator.py index 39b5694..4e5e2e6 100644 --- a/flaschengeist/system/decorator.py +++ b/flaschengeist/system/decorator.py @@ -6,12 +6,12 @@ from flaschengeist import logger from flaschengeist.system.controller import sessionController -def login_required(permissions=None): +def login_required(permission=None): def wrap(func): @wraps(func) def wrapped_f(*args, **kwargs): token = list(filter(None, request.headers.get("Authorization").split(" ")))[-1] - access_token = sessionController.validate_token(token, request.user_agent, permissions) + access_token = sessionController.validate_token(token, request.user_agent, permission) if access_token: kwargs["access_token"] = access_token logger.debug("token {{ {} }} is valid".format(token)) @@ -19,5 +19,7 @@ def login_required(permissions=None): else: logger.info("token {{ {} }} is not valid".format(token)) raise Unauthorized + return wrapped_f + return wrap diff --git a/flaschengeist/system/models/event.py b/flaschengeist/system/models/event.py index 94b6e50..5a28ee8 100644 --- a/flaschengeist/system/models/event.py +++ b/flaschengeist/system/models/event.py @@ -45,15 +45,19 @@ class Event(db.Model, ModelSerializeMixin): class Job(db.Model, ModelSerializeMixin): __tablename__ = "job" - _user: User = db.relationship("User") - # user: str = column_property(_user.userid) + user: str = None value: float = db.Column(db.Numeric(precision=3, scale=2)) _id = db.Column("id", db.Integer, primary_key=True) _user_id = db.Column("user_id", db.Integer, db.ForeignKey("user.id")) + _user: User = db.relationship("User") _slot_id = db.Column("slot_id", db.Integer, db.ForeignKey("job_slot.id")) _slot = db.relationship("JobSlot") + @property + def user(self): + return self._user.userid + class JobKind(db.Model, ModelSerializeMixin): __tablename__ = "job_kind"