From 4cd68d7e81c31a1f0fea0fd0d867c62efac32fc1 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sun, 4 Oct 2020 01:25:50 +0200 Subject: [PATCH] Added Role controller --- flaschengeist/modules/auth_ldap/__init__.py | 2 +- .../modules/{ => geruecht}/creditList.py | 0 .../{ => geruecht}/finanzer/__init__.py | 0 .../modules/{ => geruecht}/finanzer/routes.py | 0 .../modules/{ => geruecht}/gastro/__init__.py | 0 .../modules/{ => geruecht}/gastro/routes.py | 0 .../{ => geruecht}/vorstand/__init__.py | 0 .../modules/{ => geruecht}/vorstand/routes.py | 0 flaschengeist/modules/roles/__init__.py | 87 +++++++++++++++++++ flaschengeist/system/config.py | 3 + .../system/controller/roleController.py | 58 +++++++++++++ flaschengeist/system/models/user.py | 42 +++++---- setup.py | 2 + 13 files changed, 177 insertions(+), 17 deletions(-) rename flaschengeist/modules/{ => geruecht}/creditList.py (100%) rename flaschengeist/modules/{ => geruecht}/finanzer/__init__.py (100%) rename flaschengeist/modules/{ => geruecht}/finanzer/routes.py (100%) rename flaschengeist/modules/{ => geruecht}/gastro/__init__.py (100%) rename flaschengeist/modules/{ => geruecht}/gastro/routes.py (100%) rename flaschengeist/modules/{ => geruecht}/vorstand/__init__.py (100%) rename flaschengeist/modules/{ => geruecht}/vorstand/routes.py (100%) create mode 100644 flaschengeist/modules/roles/__init__.py create mode 100644 flaschengeist/system/controller/roleController.py diff --git a/flaschengeist/modules/auth_ldap/__init__.py b/flaschengeist/modules/auth_ldap/__init__.py index 8668975..3f3c5c4 100644 --- a/flaschengeist/modules/auth_ldap/__init__.py +++ b/flaschengeist/modules/auth_ldap/__init__.py @@ -58,7 +58,7 @@ class AuthLDAP(modules.Auth): if 'displayName' in r: user.display_name = r['displayName'][0] for group in self._get_groups(user.uid): - user.add_group(group) + user.add_role(group) def _get_groups(self, uid): groups = [] diff --git a/flaschengeist/modules/creditList.py b/flaschengeist/modules/geruecht/creditList.py similarity index 100% rename from flaschengeist/modules/creditList.py rename to flaschengeist/modules/geruecht/creditList.py diff --git a/flaschengeist/modules/finanzer/__init__.py b/flaschengeist/modules/geruecht/finanzer/__init__.py similarity index 100% rename from flaschengeist/modules/finanzer/__init__.py rename to flaschengeist/modules/geruecht/finanzer/__init__.py diff --git a/flaschengeist/modules/finanzer/routes.py b/flaschengeist/modules/geruecht/finanzer/routes.py similarity index 100% rename from flaschengeist/modules/finanzer/routes.py rename to flaschengeist/modules/geruecht/finanzer/routes.py diff --git a/flaschengeist/modules/gastro/__init__.py b/flaschengeist/modules/geruecht/gastro/__init__.py similarity index 100% rename from flaschengeist/modules/gastro/__init__.py rename to flaschengeist/modules/geruecht/gastro/__init__.py diff --git a/flaschengeist/modules/gastro/routes.py b/flaschengeist/modules/geruecht/gastro/routes.py similarity index 100% rename from flaschengeist/modules/gastro/routes.py rename to flaschengeist/modules/geruecht/gastro/routes.py diff --git a/flaschengeist/modules/vorstand/__init__.py b/flaschengeist/modules/geruecht/vorstand/__init__.py similarity index 100% rename from flaschengeist/modules/vorstand/__init__.py rename to flaschengeist/modules/geruecht/vorstand/__init__.py diff --git a/flaschengeist/modules/vorstand/routes.py b/flaschengeist/modules/geruecht/vorstand/routes.py similarity index 100% rename from flaschengeist/modules/vorstand/routes.py rename to flaschengeist/modules/geruecht/vorstand/routes.py diff --git a/flaschengeist/modules/roles/__init__.py b/flaschengeist/modules/roles/__init__.py new file mode 100644 index 0000000..a011f7e --- /dev/null +++ b/flaschengeist/modules/roles/__init__.py @@ -0,0 +1,87 @@ +from flask import Blueprint, request, jsonify +from werkzeug.exceptions import NotFound, BadRequest, Forbidden + +from flaschengeist.system.decorator import login_required +from flaschengeist.system.controller import roleController + +roles_bp = Blueprint("roles", __name__) +permissions = {} + + +def register(): + return roles_bp, permissions + +###################################################### +# Routes # +# # +# /roles POST: register new # +# GET: get all roles # +# /roles/permissions GET: get all permissions # +# /roles/ GET: get role with rid # +# PUT: modify role / permission # +# DELETE: remove role # +###################################################### + + +@roles_bp.route("/roles", methods=['POST']) +@login_required() +def __add_role(): + data = request.get_json() + if not data or "name" not in data: + raise BadRequest + if "permissions" in data: + permissions = data["permissions"] + role = roleController.create_role(data["name"], permissions) + return jsonify({"ok": "ok", "id": role.id}) + + +@roles_bp.route("/roles", methods=['GET']) +@login_required() +def __list_roles(**kwargs): + roles = roleController.get_roles() + return jsonify(roles) + + +@roles_bp.route("/roles/permissions", methods=['GET']) +@login_required() +def __list_permissions(**kwargs): + permissions = roleController.get_permissions() + return jsonify(permissions) + + +@roles_bp.route("/roles/", methods=['GET']) +@login_required() +def __get_role(rid, **kwargs): + role = roleController.get_role(rid) + if role: + return jsonify({ + "id": role.id, + "name": role, + "permissions": role.permissions + }) + raise NotFound + + +@roles_bp.route("/roles/", methods=['PUT']) +@login_required() +def __edit_role(rid, **kwargs): + role = roleController.get_role(rid) + if not role: + raise NotFound + + data = request.get_json() + if 'name' in data: + role.name = data["name"] + if "permissions" in data: + roleController.set_permissions(role, data["permissions"]) + roleController.update_role(role) + return jsonify({"ok": "ok"}) + + +@roles_bp.route("/roles/", methods=['DELETE']) +@login_required() +def __delete_role(rid, **kwargs): + if not roleController.delete_role(rid): + raise NotFound + + return jsonify({"ok": "ok"}) diff --git a/flaschengeist/system/config.py b/flaschengeist/system/config.py index e8181a2..479b908 100644 --- a/flaschengeist/system/config.py +++ b/flaschengeist/system/config.py @@ -28,6 +28,9 @@ config.read_dict({ 'auth': { 'enabled': True }, + 'roles': { + 'enabled': True + }, 'users': { 'enabled': True } diff --git a/flaschengeist/system/controller/roleController.py b/flaschengeist/system/controller/roleController.py new file mode 100644 index 0000000..cdea0bc --- /dev/null +++ b/flaschengeist/system/controller/roleController.py @@ -0,0 +1,58 @@ +from flask import current_app +from sqlalchemy.exc import IntegrityError +from werkzeug.exceptions import BadRequest + +from flaschengeist.system.models.user import Role, Permission +from flaschengeist.system.database import db +from flaschengeist import logger + + +def get_roles(): + return Role.query.all() + + +def get_role(rid): + return Role.query.get(rid) + + +def get_permissions(): + return Permission.query.all() + + +def update_role(role): + db.session.commit() + + +def set_permissions(role, permissions): + for name in permissions: + p = Permission.query.filter(Permission.name == name).one_or_none() + if not p: + raise BadRequest("Invalid permission name >{}<".format(name)) + role.permissions.append(p) + db.session.commit() + + +def create_permissions(permissions): + for permission in permissions: + if Permission.query.filter(Permission.name == permission).count() > 0: + continue + p = Permission(name=permission) + db.session.add(p) + db.session.commit() + + +def create_role(name, permissions=[]): + role = Role(name=name) + db.session.add(role) + set_permissions(role, permissions) + return role.id + + +def delete_role(id): + try: + num = Role.query.filter(Role.id == id).delete() + except IntegrityError: + logger.debug("IntegrityError: Role might still be in use", exc_info=True) + raise BadRequest("Role still in use") + db.session.commit() + return num == 1 diff --git a/flaschengeist/system/models/user.py b/flaschengeist/system/models/user.py index e028781..351f6f3 100644 --- a/flaschengeist/system/models/user.py +++ b/flaschengeist/system/models/user.py @@ -5,9 +5,9 @@ from werkzeug.local import LocalProxy logger = LocalProxy(lambda: current_app.logger) -association_table = db.Table('user_group', +association_table = db.Table('user_x_role', db.Column('user_id', db.Integer, db.ForeignKey('user.id')), - db.Column('group_id', db.Integer, db.ForeignKey('group.id')) + db.Column('role_id', db.Integer, db.ForeignKey('role.id')) ) @@ -31,7 +31,7 @@ class User(db.Model): firstname = db.Column(db.String(30)) lastname = db.Column(db.String(30)) mail = db.Column(db.String(30)) - groups = db.relationship("Group", secondary=association_table) + roles = db.relationship("Role", secondary=association_table) sessions = db.relationship("AccessToken", back_populates="user") attributes = db.relationship("UserAttribute", collection_class=attribute_mapped_collection('name'), cascade="all, delete") @@ -42,11 +42,11 @@ class User(db.Model): else: self.attributes[name] = UserAttribute(name=name, value=value) - def add_group(self, name): - r = Group.query.filter_by(name=name).first() + def add_role(self, name): + r = Role.query.filter_by(name=name).first() if not r: - r = Group(name=name) - self.groups.append(r) + r = Role(name=name) + self.roles.append(r) def update_data(self, data): logger.debug("update data of user") @@ -61,6 +61,13 @@ class User(db.Model): if 'display_name' in data: self.display_name = data['display_name'] + def has_permissions(self, permissions): + for role in self.roles: + for permission in role.permissions: + if permission.name in permissions: + return True + return False + def serialize(self): return { # TODO: username should be UID? @@ -69,29 +76,29 @@ class User(db.Model): "firstname": self.firstname, "lastname": self.lastname, "mail": self.mail, - "groups": ["user"] + [g.name for g in self.groups] + "roles": ["user"] + [r.name for r in self.roles] } class UserAttribute(db.Model): - __tablename__ = 'userAttribute' + __tablename__ = 'user_attribute' id = db.Column(db.Integer, primary_key=True) user = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) name = db.Column(db.String(30)) value = db.Column(db.String(192)) -group_permission_association_table = db.Table('group_permission', - db.Column('group_id', db.Integer, db.ForeignKey('group.id')), +role_permission_association_table = db.Table('role_x_permission', + db.Column('role_id', db.Integer, db.ForeignKey('role.id')), db.Column('permission_id', db.Integer, db.ForeignKey('permission.id')) ) -class Group(db.Model): - __tablename__ = 'group' +class Role(db.Model): + __tablename__ = 'role' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(30)) - permissions = db.relationship("Permission", secondary=group_permission_association_table) + name = db.Column(db.String(30), unique=True) + permissions = db.relationship("Permission", secondary=role_permission_association_table, cascade="all, delete") def serialize(self): return self.name @@ -100,4 +107,7 @@ class Group(db.Model): class Permission(db.Model): __tablename__ = 'permission' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(30)) + name = db.Column(db.String(30), unique=True) + + def serialize(self): + return self.name diff --git a/setup.py b/setup.py index 23b76be..706a52b 100644 --- a/setup.py +++ b/setup.py @@ -18,12 +18,14 @@ setup( "flask_cors", "werkzeug", "bjoern", + "python-dateutil" ], extras_require={"ldap": ["flask_ldapconn", "ldap3"]}, entry_points={ "flaschengeist.plugin": [ "auth = flaschengeist.modules.auth:register", "users = flaschengeist.modules.users:register", + "roles = flaschengeist.modules.roles:register", "schedule = flaschengeist.modules.schedule:register", ], "flaschengeist.auth": [