[System][Plugin] Improved Hooks, roles and auth_ldap improvements

* Hooks now allow multiple hooked functions
* Hooks can now be called before and after a function call
* Fixed issue in datetime util when string is None or empty
* Roles: Return new created role as json
* auth_ldap: Use new Hooks
* auth_ldap: Fixed an issue where ldap response is not checked (when role gets renamed)
This commit is contained in:
Ferdinand Thiessen 2020-11-18 00:39:25 +01:00
parent 58e121473b
commit 2d6b86e2eb
7 changed files with 72 additions and 57 deletions

View File

@ -26,21 +26,21 @@ def get_permissions():
@Hook
def role_updated(role, old_name):
"""Hook used when roles are updated"""
pass
def rename(role, new_name):
if role.name == new_name:
return
if db.session.query(db.exists().where(Role.name == new_name)).scalar():
raise BadRequest("Name already used")
old_name = role.name
role.name = new_name
role_updated(role, old_name)
db.session.commit()
def update_role(role, new_name):
if new_name is None:
try:
logger.debug(f"Hallo, dies ist die {role.serialize()}")
db.session.delete(role)
logger.debug(f"Hallo, dies ist die {role.serialize()}")
db.session.commit()
except IntegrityError:
logger.debug("IntegrityError: Role might still be in use", exc_info=True)
raise BadRequest("Role still in use")
elif role.name != new_name:
if db.session.query(db.exists().where(Role.name == new_name)).scalar():
raise BadRequest("Name already used")
role.name = new_name
db.session.commit()
def set_permissions(role, permissions):
@ -63,18 +63,15 @@ def create_permissions(permissions):
def create_role(name: str, permissions=[]):
logger.debug(f"Create new role with name: {name}")
role = Role(name=name)
db.session.add(role)
set_permissions(role, permissions)
db.session.commit()
logger.debug(f"Created role: {role.serialize()}")
return role
def delete(role):
role.permissions.clear()
try:
db.session.delete(role)
db.session.commit()
except IntegrityError:
logger.debug("IntegrityError: Role might still be in use", exc_info=True)
raise BadRequest("Role still in use")
role_updated(None, role.name)
update_role(role, None)

View File

@ -101,5 +101,5 @@ def save_avatar(user, avatar):
user.avatar_url = ""
r = current_app.config["FG_AUTH_BACKEND"].set_avatar(user, avatar)
if not user.avatar_url:
user.avatar_url = url_for('users.get_avatar', userid=user.userid)
user.avatar_url = url_for("users.get_avatar", userid=user.userid)
db.session.commit()

View File

@ -2,22 +2,21 @@ import pkg_resources
from werkzeug.exceptions import MethodNotAllowed, NotFound
from flaschengeist.models.user import _Avatar
from flaschengeist.utils.hook import HookCall
from flaschengeist.utils.hook import HookBefore, HookAfter
send_message_hook = HookCall("send_message")
"""Hook decorator for sending messages, register to send the message
Args:
message: Message object to send
"""
role_updated = HookCall("role_updated")
before_role_updated = HookBefore("update_role")
"""Hook decorator for when roles are modified
Args:
role: Role object containing the modified role (None if deleted)
old_name: Old name if the name was changed
role: Role object to modify
new_name: New name if the name was changed (None if delete)
"""
update_user_hook = HookCall("update_user")
after_role_updated = HookAfter("update_role")
"""Hook decorator for when roles are modified
Args:
role: Role object containing the modified role
new_name: New name if the name was changed (None if deleted)
"""
before_update_user = HookBefore("update_user")
"""Hook decorator, when ever an user update is done, this is called before.
Args:
user: User object

View File

@ -10,7 +10,7 @@ from ldap3 import SUBTREE, MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE, HASHED_SAL
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
from flaschengeist import logger
from flaschengeist.plugins import AuthPlugin, role_updated
from flaschengeist.plugins import AuthPlugin, after_role_updated
from flaschengeist.models.user import User, Role, _Avatar
import flaschengeist.controller.userController as userController
@ -43,9 +43,9 @@ class AuthLDAP(AuthPlugin):
else:
self.admin_dn = None
@role_updated
def _role_updated(role, old_name):
self.__modify_role(role, old_name)
@after_role_updated
def _role_updated(role, new_name):
self.__modify_role(role, new_name)
def login(self, user, password):
if not user:
@ -183,19 +183,19 @@ class AuthLDAP(AuthPlugin):
def __modify_role(
self,
role: Optional[Role],
old_name: str,
role: Role,
new_name: Optional[str],
):
if self.admin_dn is None:
logger.error("admin_dn missing in ldap config!")
raise InternalServerError
try:
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
ldap_conn.search(f"ou=group,{self.dn}", f"(cn={old_name})", SUBTREE, attributes=["cn"])
if len(ldap_conn.response) >= 0:
ldap_conn.search(f"ou=group,{self.dn}", f"(cn={role.name})", SUBTREE, attributes=["cn"])
if len(ldap_conn.response) > 0:
dn = ldap_conn.response[0]["dn"]
if role:
ldap_conn.modify_dn(dn, f"cn={role.name}")
if new_name:
ldap_conn.modify_dn(dn, f"cn={new_name}")
else:
ldap_conn.delete(dn)

View File

@ -10,6 +10,7 @@ from http.client import CREATED, NO_CONTENT
from flaschengeist.plugins import Plugin
from flaschengeist.decorator import login_required
from flaschengeist.controller import roleController
from flaschengeist.utils.HTTP import created
roles_bp = Blueprint("roles", __name__)
_permission_edit = "roles_edit"
@ -51,15 +52,14 @@ def create_role(current_session):
current_session: Session sent with Authorization Header
Returns:
HTTP-201 or HTTP error
HTTP-201 and json encoded created Role or HTTP error
"""
data = request.get_json()
if not data or "name" not in data:
raise BadRequest
if "permissions" in data:
permissions = data["permissions"]
roleController.create_role(data["name"], permissions)
return "", CREATED
return created(roleController.create_role(data["name"], permissions))
@roles_bp.route("/roles/permissions", methods=["GET"])
@ -116,10 +116,10 @@ def edit_role(role_id, current_session):
role = roleController.get(role_id)
data = request.get_json()
if "name" in data:
roleController.rename(role, data["name"])
if "permissions" in data:
roleController.set_permissions(role, data["permissions"])
if "name" in data:
roleController.update_role(role, data["name"])
return "", NO_CONTENT

View File

@ -6,6 +6,8 @@ MonkeyPatch.patch_fromisoformat()
def from_iso_format(date_str):
"""Z-suffix aware version of `datetime.datetime.fromisoformat`"""
if not date_str:
return None
time = datetime.datetime.fromisoformat(date_str.replace("Z", "+00:00"))
if time.tzinfo:
return time.astimezone(datetime.timezone.utc)

View File

@ -1,4 +1,4 @@
_hook_dict = {}
_hook_dict = ({}, {})
class Hook(object):
@ -10,17 +10,34 @@ class Hook(object):
self.function = function
def __call__(self, *args, **kwargs):
if self.function.__name__ in _hook_dict:
_hook_dict[self.function.__name__](*args, **kwargs)
self.function(*args, **kwargs)
# Hooks before
for function in _hook_dict[0].get(self.function.__name__, []):
function(*args, **kwargs)
# Main function
ret = self.function(*args, **kwargs)
# Hooks after
for function in _hook_dict[1].get(self.function.__name__, []):
function(*args, **kwargs)
return ret
class HookCall(object):
"""Decorator for functions to be called if a Hook is called"""
class HookBefore(object):
"""Decorator for functions to be called before a Hook-Function is called"""
def __init__(self, name):
self.name = name
def __call__(self, function):
_hook_dict[self.name] = function
_hook_dict[0].setdefault(self.name, []).append(function)
return function
class HookAfter(object):
"""Decorator for functions to be called after a Hook-Function is called"""
def __init__(self, name):
self.name = name
def __call__(self, function):
_hook_dict[1].setdefault(self.name, []).append(function)
return function