diff --git a/flaschengeist.example.toml b/flaschengeist.example.toml index c177f23..6953678 100644 --- a/flaschengeist.example.toml +++ b/flaschengeist.example.toml @@ -24,23 +24,23 @@ enabled = true #[auth_ldap] # enabled = true -# URL = -# PORT = -# BINDDN = -# BASEDN = -# SECRET = -# USE_SSL = -# ADMIN_DN = -# ADMIN_SECRET = -# gidNumber = +# host = +# port = +# bind_dn = +# base_dn = +# secret = +# use_ssl = +# admin_dn = +# admin_dn = +# default_gid = #[users] -# allways enabled +# always enabled # ## allowed values: false, "managed", "public" ## false: Disable registration ## "managed": only users with matching permission are allowed to register new users -## "public": Also unautheticated users can register an account +## "public": Also unauthenticated users can register an account # registration = False ############################ diff --git a/flaschengeist/controller/roleController.py b/flaschengeist/controller/roleController.py index 185bffa..b7e3039 100644 --- a/flaschengeist/controller/roleController.py +++ b/flaschengeist/controller/roleController.py @@ -1,3 +1,4 @@ +from flask import current_app from sqlalchemy.exc import IntegrityError from werkzeug.exceptions import BadRequest, NotFound @@ -24,7 +25,12 @@ def get_permissions(): return Permission.query.all() -def update_role(role): +def rename(role, new_name): + if db.session.query(db.exists().where(Role.name == new_name)).scalar(): + raise BadRequest("Name already used") + + current_app.config["FG_AUTH_BACKEND"].modify_role(role.name, new_name) + role.name = new_name db.session.commit() @@ -62,3 +68,4 @@ def delete(role): except IntegrityError: logger.debug("IntegrityError: Role might still be in use", exc_info=True) raise BadRequest("Role still in use") + current_app.config["FG_AUTH_BACKEND"].modify_role(role.name, None) diff --git a/flaschengeist/plugins/__init__.py b/flaschengeist/plugins/__init__.py index 349cc32..2082116 100644 --- a/flaschengeist/plugins/__init__.py +++ b/flaschengeist/plugins/__init__.py @@ -1,4 +1,5 @@ import pkg_resources +from typing import Optional from werkzeug.exceptions import MethodNotAllowed from flaschengeist.utils.hook import HookCall @@ -67,6 +68,7 @@ class AuthPlugin(Plugin): def modify_user(self, user, password, new_password=None): """If backend is using (writeable) external data, then update the external database with the user provided. + User might have roles not existing on the external database, so you might have to create those. Args: user: User object @@ -79,6 +81,16 @@ class AuthPlugin(Plugin): """ raise NotImplemented + def modify_role(self, old_name: str, new_name: Optional[str]): + """A call to this function indicated that a role was deleted (and has no users) + Might be used if modify_user is implemented. + + Args: + old_name: Name of the modified role + new_name: New role name or None if deleted + """ + pass + def create_user(self, user, password): """If backend is using (writeable) external data, then create a new user on the external database. diff --git a/flaschengeist/plugins/auth_ldap/__init__.py b/flaschengeist/plugins/auth_ldap/__init__.py index 0eb27c3..f37bb17 100644 --- a/flaschengeist/plugins/auth_ldap/__init__.py +++ b/flaschengeist/plugins/auth_ldap/__init__.py @@ -6,8 +6,9 @@ from ldap3 import SUBTREE, MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE, HASHED_SAL from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError from flask import current_app as app from flask_ldapconn import LDAPConn -from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequest, InternalServerError +from flaschengeist import logger from flaschengeist.plugins import AuthPlugin from flaschengeist.models.user import User import flaschengeist.controller.userController as userController @@ -17,28 +18,30 @@ class AuthLDAP(AuthPlugin): def __init__(self, cfg): super().__init__() - config = {"PORT": 389, "USE_SSL": False} + config = {"port": 389, "use_ssl": False} config.update(cfg) app.config.update( - LDAP_SERVER=config["URL"], - LDAP_PORT=config["PORT"], - LDAP_BINDDN=config["BINDDN"], + LDAP_SERVER=config["host"], + LDAP_PORT=config["port"], + LDAP_BINDDN=config["bind_dn"], LDAP_USE_TLS=False, - LDAP_USE_SSL=config["USE_SSL"], + LDAP_USE_SSL=config["use_ssl"], LDAP_TLS_VERSION=ssl.PROTOCOL_TLSv1_2, LDAP_REQUIRE_CERT=ssl.CERT_NONE, FORCE_ATTRIBUTE_VALUE_AS_LIST=True, ) if "SECRET" in config: - app.config["LDAP_SECRET"] = (config["SECRET"],) + app.config["LDAP_SECRET"] = (config["secret"],) self.ldap = LDAPConn(app) - self.dn = config["BASEDN"] - self.gidNumber = config['gidNumber'] + self.dn = config["base_dn"] + self.default_gid = config['default_gid'] # TODO: might not be set if modify is called - if "ADMIN_DN" in config: - self.admin_dn = config["ADMIN_DN"] - self.admin_secret = config["ADMIN_SECRET"] + if "admin_dn" in config: + self.admin_dn = config["admin_dn"] + self.admin_secret = config["admin_secret"] + else: + self.admin_dn = None def login(self, user, password): if not user: @@ -64,6 +67,10 @@ class AuthLDAP(AuthPlugin): userController.set_roles(user, self._get_groups(user.userid), create=True) def create_user(self, user, password): + 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) self.ldap.connection.search( @@ -76,7 +83,7 @@ class AuthLDAP(AuthPlugin): attributes = { 'sn': user.firstname, 'givenName': user.lastname, - 'gidNumber': self.gidNumber, + 'gidNumber': self.default_gid, 'homeDirectory': f'/home/{user.userid}', 'loginShell': '/bin/bash', 'uid': user.userid, @@ -85,30 +92,12 @@ class AuthLDAP(AuthPlugin): } ldap_conn.add(dn, object_class, attributes) - self.set_roles(user) + self._set_roles(user) except (LDAPPasswordIsMandatoryError, LDAPBindError): raise BadRequest - except Exception as e: - pass def _get_groups(self, uid): groups = [] - - self.ldap.connection.search( - "ou=user,{}".format(self.dn), "(uid={})".format(uid), SUBTREE, attributes=["gidNumber"] - ) - - # Maingroup ist uninteressant - - #main_group_number = self.ldap.connection.response[0]["attributes"]["gidNumber"] - #if main_group_number: - # if type(main_group_number) is list: - # main_group_number = main_group_number[0] - # self.ldap.connection.search( - # "ou=group,{}".format(self.dn), "(gidNumber={})".format(main_group_number), attributes=["cn"] - # ) - # groups.append(self.ldap.connection.response[0]["attributes"]["cn"][0]) - self.ldap.connection.search( "ou=group,{}".format(self.dn), "(memberUID={})".format(uid), SUBTREE, attributes=["cn"] ) @@ -117,18 +106,18 @@ class AuthLDAP(AuthPlugin): groups.append(data["attributes"]["cn"][0]) return groups - def set_roles(self, user: User): + def _set_roles(self, user: User): try: ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret) self.ldap.connection.search(f"ou=group,{self.dn}", "(cn=*)", SUBTREE, attributes=["cn", "gidNumber"]) ldap_roles = self.ldap.response() - gidNumbers = sorted(ldap_roles, key=lambda i: i['attributes']['gidNumber'], reverse=True) - gidNumber = gidNumbers[0]['attributes']['gidNumber'] + 1 + gid_numbers = sorted(ldap_roles, key=lambda i: i['attributes']['gidNumber'], reverse=True) + gid_number = gid_numbers[0]['attributes']['gidNumber'] + 1 for user_role in user.roles: if user_role not in [role["attributes"]["cn"][0] for role in ldap_roles]: - ldap_conn.add(f"cn={user_role},ou=group,{self.dn}", ["posixGroup"], attributes={"gidNumber": gidNumber}) + ldap_conn.add(f"cn={user_role},ou=group,{self.dn}", ["posixGroup"], attributes={"gidNumber": gid_number}) ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret) self.ldap.connection.search(f"ou=group,{self.dn}", "(cn=*)", SUBTREE, attributes=["cn", "gidNumber"]) @@ -144,8 +133,11 @@ class AuthLDAP(AuthPlugin): except (LDAPPasswordIsMandatoryError, LDAPBindError): raise BadRequest - def modify_user(self, user: User, password=None, new_password=None): + if self.admin_dn is None: + logger.error("admin_dn missing in ldap config!") + raise InternalServerError + try: dn = user.get_attribute("DN") if password: @@ -166,6 +158,6 @@ class AuthLDAP(AuthPlugin): salted_password = hashed(HASHED_SALTED_MD5, new_password) modifier["userPassword"] = [(MODIFY_REPLACE, [salted_password])] ldap_conn.modify(dn, modifier) - self.set_roles(user) + self._set_roles(user) except (LDAPPasswordIsMandatoryError, LDAPBindError): raise BadRequest diff --git a/flaschengeist/plugins/roles/__init__.py b/flaschengeist/plugins/roles/__init__.py index ee8c6d4..15ebe47 100644 --- a/flaschengeist/plugins/roles/__init__.py +++ b/flaschengeist/plugins/roles/__init__.py @@ -116,10 +116,9 @@ def edit_role(role_id, current_session): data = request.get_json() if "name" in data: - role.name = data["name"] + roleController.rename(role, data["name"]) if "permissions" in data: roleController.set_permissions(role, data["permissions"]) - roleController.update_role(role) return "", NO_CONTENT