[Plugin] Added plugin function when roles are modified
LDAP: Use same config style as the rest.
This commit is contained in:
		
							parent
							
								
									96765ee932
								
							
						
					
					
						commit
						cbcd5b39a3
					
				| 
						 | 
					@ -24,23 +24,23 @@ enabled = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[auth_ldap]
 | 
					#[auth_ldap]
 | 
				
			||||||
# enabled = true
 | 
					# enabled = true
 | 
				
			||||||
# URL =
 | 
					# host =
 | 
				
			||||||
# PORT =
 | 
					# port =
 | 
				
			||||||
# BINDDN =
 | 
					# bind_dn =
 | 
				
			||||||
# BASEDN =
 | 
					# base_dn =
 | 
				
			||||||
# SECRET =
 | 
					# secret =
 | 
				
			||||||
# USE_SSL =
 | 
					# use_ssl =
 | 
				
			||||||
# ADMIN_DN =
 | 
					# admin_dn =
 | 
				
			||||||
# ADMIN_SECRET =
 | 
					# admin_dn =
 | 
				
			||||||
# gidNumber =
 | 
					# default_gid =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[users]
 | 
					#[users]
 | 
				
			||||||
# allways enabled
 | 
					# always enabled
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
## allowed values: false, "managed", "public"
 | 
					## allowed values: false, "managed", "public"
 | 
				
			||||||
## false: Disable registration
 | 
					## false: Disable registration
 | 
				
			||||||
## "managed": only users with matching permission are allowed to register new users
 | 
					## "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
 | 
					# registration = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
############################
 | 
					############################
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					from flask import current_app
 | 
				
			||||||
from sqlalchemy.exc import IntegrityError
 | 
					from sqlalchemy.exc import IntegrityError
 | 
				
			||||||
from werkzeug.exceptions import BadRequest, NotFound
 | 
					from werkzeug.exceptions import BadRequest, NotFound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +25,12 @@ def get_permissions():
 | 
				
			||||||
    return Permission.query.all()
 | 
					    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()
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,3 +68,4 @@ def delete(role):
 | 
				
			||||||
    except IntegrityError:
 | 
					    except IntegrityError:
 | 
				
			||||||
        logger.debug("IntegrityError: Role might still be in use", exc_info=True)
 | 
					        logger.debug("IntegrityError: Role might still be in use", exc_info=True)
 | 
				
			||||||
        raise BadRequest("Role still in use")
 | 
					        raise BadRequest("Role still in use")
 | 
				
			||||||
 | 
					    current_app.config["FG_AUTH_BACKEND"].modify_role(role.name, None)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import pkg_resources
 | 
					import pkg_resources
 | 
				
			||||||
 | 
					from typing import Optional
 | 
				
			||||||
from werkzeug.exceptions import MethodNotAllowed
 | 
					from werkzeug.exceptions import MethodNotAllowed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from flaschengeist.utils.hook import HookCall
 | 
					from flaschengeist.utils.hook import HookCall
 | 
				
			||||||
| 
						 | 
					@ -67,6 +68,7 @@ class AuthPlugin(Plugin):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def modify_user(self, user, password, new_password=None):
 | 
					    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.
 | 
					        """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:
 | 
					        Args:
 | 
				
			||||||
            user: User object
 | 
					            user: User object
 | 
				
			||||||
| 
						 | 
					@ -79,6 +81,16 @@ class AuthPlugin(Plugin):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        raise NotImplemented
 | 
					        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):
 | 
					    def create_user(self, user, password):
 | 
				
			||||||
        """If backend is using (writeable) external data, then create a new user on the external database.
 | 
					        """If backend is using (writeable) external data, then create a new user on the external database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,9 @@ from ldap3 import SUBTREE, MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE, HASHED_SAL
 | 
				
			||||||
from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError
 | 
					from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError
 | 
				
			||||||
from flask import current_app as app
 | 
					from flask import current_app as app
 | 
				
			||||||
from flask_ldapconn import LDAPConn
 | 
					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.plugins import AuthPlugin
 | 
				
			||||||
from flaschengeist.models.user import User
 | 
					from flaschengeist.models.user import User
 | 
				
			||||||
import flaschengeist.controller.userController as userController
 | 
					import flaschengeist.controller.userController as userController
 | 
				
			||||||
| 
						 | 
					@ -17,28 +18,30 @@ class AuthLDAP(AuthPlugin):
 | 
				
			||||||
    def __init__(self, cfg):
 | 
					    def __init__(self, cfg):
 | 
				
			||||||
        super().__init__()
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        config = {"PORT": 389, "USE_SSL": False}
 | 
					        config = {"port": 389, "use_ssl": False}
 | 
				
			||||||
        config.update(cfg)
 | 
					        config.update(cfg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        app.config.update(
 | 
					        app.config.update(
 | 
				
			||||||
            LDAP_SERVER=config["URL"],
 | 
					            LDAP_SERVER=config["host"],
 | 
				
			||||||
            LDAP_PORT=config["PORT"],
 | 
					            LDAP_PORT=config["port"],
 | 
				
			||||||
            LDAP_BINDDN=config["BINDDN"],
 | 
					            LDAP_BINDDN=config["bind_dn"],
 | 
				
			||||||
            LDAP_USE_TLS=False,
 | 
					            LDAP_USE_TLS=False,
 | 
				
			||||||
            LDAP_USE_SSL=config["USE_SSL"],
 | 
					            LDAP_USE_SSL=config["use_ssl"],
 | 
				
			||||||
            LDAP_TLS_VERSION=ssl.PROTOCOL_TLSv1_2,
 | 
					            LDAP_TLS_VERSION=ssl.PROTOCOL_TLSv1_2,
 | 
				
			||||||
            LDAP_REQUIRE_CERT=ssl.CERT_NONE,
 | 
					            LDAP_REQUIRE_CERT=ssl.CERT_NONE,
 | 
				
			||||||
            FORCE_ATTRIBUTE_VALUE_AS_LIST=True,
 | 
					            FORCE_ATTRIBUTE_VALUE_AS_LIST=True,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if "SECRET" in config:
 | 
					        if "SECRET" in config:
 | 
				
			||||||
            app.config["LDAP_SECRET"] = (config["SECRET"],)
 | 
					            app.config["LDAP_SECRET"] = (config["secret"],)
 | 
				
			||||||
        self.ldap = LDAPConn(app)
 | 
					        self.ldap = LDAPConn(app)
 | 
				
			||||||
        self.dn = config["BASEDN"]
 | 
					        self.dn = config["base_dn"]
 | 
				
			||||||
        self.gidNumber = config['gidNumber']
 | 
					        self.default_gid = config['default_gid']
 | 
				
			||||||
        # TODO: might not be set if modify is called
 | 
					        # TODO: might not be set if modify is called
 | 
				
			||||||
        if "ADMIN_DN" in config:
 | 
					        if "admin_dn" in config:
 | 
				
			||||||
            self.admin_dn = config["ADMIN_DN"]
 | 
					            self.admin_dn = config["admin_dn"]
 | 
				
			||||||
            self.admin_secret = config["ADMIN_SECRET"]
 | 
					            self.admin_secret = config["admin_secret"]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.admin_dn = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def login(self, user, password):
 | 
					    def login(self, user, password):
 | 
				
			||||||
        if not user:
 | 
					        if not user:
 | 
				
			||||||
| 
						 | 
					@ -64,6 +67,10 @@ class AuthLDAP(AuthPlugin):
 | 
				
			||||||
            userController.set_roles(user, self._get_groups(user.userid), create=True)
 | 
					            userController.set_roles(user, self._get_groups(user.userid), create=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_user(self, user, password):
 | 
					    def create_user(self, user, password):
 | 
				
			||||||
 | 
					        if self.admin_dn is None:
 | 
				
			||||||
 | 
					            logger.error("admin_dn missing in ldap config!")
 | 
				
			||||||
 | 
					            raise InternalServerError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
 | 
					            ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
 | 
				
			||||||
            self.ldap.connection.search(
 | 
					            self.ldap.connection.search(
 | 
				
			||||||
| 
						 | 
					@ -76,7 +83,7 @@ class AuthLDAP(AuthPlugin):
 | 
				
			||||||
            attributes = {
 | 
					            attributes = {
 | 
				
			||||||
                'sn': user.firstname,
 | 
					                'sn': user.firstname,
 | 
				
			||||||
                'givenName': user.lastname,
 | 
					                'givenName': user.lastname,
 | 
				
			||||||
                'gidNumber': self.gidNumber,
 | 
					                'gidNumber': self.default_gid,
 | 
				
			||||||
                'homeDirectory': f'/home/{user.userid}',
 | 
					                'homeDirectory': f'/home/{user.userid}',
 | 
				
			||||||
                'loginShell': '/bin/bash',
 | 
					                'loginShell': '/bin/bash',
 | 
				
			||||||
                'uid': user.userid,
 | 
					                'uid': user.userid,
 | 
				
			||||||
| 
						 | 
					@ -85,30 +92,12 @@ class AuthLDAP(AuthPlugin):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ldap_conn.add(dn, object_class, attributes)
 | 
					            ldap_conn.add(dn, object_class, attributes)
 | 
				
			||||||
            self.set_roles(user)
 | 
					            self._set_roles(user)
 | 
				
			||||||
        except (LDAPPasswordIsMandatoryError, LDAPBindError):
 | 
					        except (LDAPPasswordIsMandatoryError, LDAPBindError):
 | 
				
			||||||
            raise BadRequest
 | 
					            raise BadRequest
 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_groups(self, uid):
 | 
					    def _get_groups(self, uid):
 | 
				
			||||||
        groups = []
 | 
					        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(
 | 
					        self.ldap.connection.search(
 | 
				
			||||||
            "ou=group,{}".format(self.dn), "(memberUID={})".format(uid), SUBTREE, attributes=["cn"]
 | 
					            "ou=group,{}".format(self.dn), "(memberUID={})".format(uid), SUBTREE, attributes=["cn"]
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -117,18 +106,18 @@ class AuthLDAP(AuthPlugin):
 | 
				
			||||||
            groups.append(data["attributes"]["cn"][0])
 | 
					            groups.append(data["attributes"]["cn"][0])
 | 
				
			||||||
        return groups
 | 
					        return groups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_roles(self, user: User):
 | 
					    def _set_roles(self, user: User):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
 | 
					            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"])
 | 
					            self.ldap.connection.search(f"ou=group,{self.dn}", "(cn=*)", SUBTREE, attributes=["cn", "gidNumber"])
 | 
				
			||||||
            ldap_roles = self.ldap.response()
 | 
					            ldap_roles = self.ldap.response()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            gidNumbers = sorted(ldap_roles, key=lambda i: i['attributes']['gidNumber'], reverse=True)
 | 
					            gid_numbers = sorted(ldap_roles, key=lambda i: i['attributes']['gidNumber'], reverse=True)
 | 
				
			||||||
            gidNumber = gidNumbers[0]['attributes']['gidNumber'] + 1
 | 
					            gid_number = gid_numbers[0]['attributes']['gidNumber'] + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for user_role in user.roles:
 | 
					            for user_role in user.roles:
 | 
				
			||||||
                if user_role not in [role["attributes"]["cn"][0] for role in ldap_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)
 | 
					            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"])
 | 
					            self.ldap.connection.search(f"ou=group,{self.dn}", "(cn=*)", SUBTREE, attributes=["cn", "gidNumber"])
 | 
				
			||||||
| 
						 | 
					@ -144,8 +133,11 @@ class AuthLDAP(AuthPlugin):
 | 
				
			||||||
        except (LDAPPasswordIsMandatoryError, LDAPBindError):
 | 
					        except (LDAPPasswordIsMandatoryError, LDAPBindError):
 | 
				
			||||||
            raise BadRequest
 | 
					            raise BadRequest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def modify_user(self, user: User, password=None, new_password=None):
 | 
					    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:
 | 
					        try:
 | 
				
			||||||
            dn = user.get_attribute("DN")
 | 
					            dn = user.get_attribute("DN")
 | 
				
			||||||
            if password:
 | 
					            if password:
 | 
				
			||||||
| 
						 | 
					@ -166,6 +158,6 @@ class AuthLDAP(AuthPlugin):
 | 
				
			||||||
                salted_password = hashed(HASHED_SALTED_MD5, new_password)
 | 
					                salted_password = hashed(HASHED_SALTED_MD5, new_password)
 | 
				
			||||||
                modifier["userPassword"] = [(MODIFY_REPLACE, [salted_password])]
 | 
					                modifier["userPassword"] = [(MODIFY_REPLACE, [salted_password])]
 | 
				
			||||||
            ldap_conn.modify(dn, modifier)
 | 
					            ldap_conn.modify(dn, modifier)
 | 
				
			||||||
            self.set_roles(user)
 | 
					            self._set_roles(user)
 | 
				
			||||||
        except (LDAPPasswordIsMandatoryError, LDAPBindError):
 | 
					        except (LDAPPasswordIsMandatoryError, LDAPBindError):
 | 
				
			||||||
            raise BadRequest
 | 
					            raise BadRequest
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,10 +116,9 @@ def edit_role(role_id, current_session):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data = request.get_json()
 | 
					    data = request.get_json()
 | 
				
			||||||
    if "name" in data:
 | 
					    if "name" in data:
 | 
				
			||||||
        role.name = data["name"]
 | 
					        roleController.rename(role, data["name"])
 | 
				
			||||||
    if "permissions" in data:
 | 
					    if "permissions" in data:
 | 
				
			||||||
        roleController.set_permissions(role, data["permissions"])
 | 
					        roleController.set_permissions(role, data["permissions"])
 | 
				
			||||||
    roleController.update_role(role)
 | 
					 | 
				
			||||||
    return "", NO_CONTENT
 | 
					    return "", NO_CONTENT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue