From 365677697d577565c622400ee2c9f3f70fc3230c Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Fri, 4 Sep 2020 00:55:23 +0200 Subject: [PATCH] Some more cleanup, added modify_user to LDAP --- flaschengeist/__init__.py | 4 +- flaschengeist/flaschengeist.example.cfg | 2 +- flaschengeist/modules/__init__.py | 35 ++-- flaschengeist/modules/auth/__init__.py | 12 +- flaschengeist/modules/auth_ldap/__init__.py | 182 ++++-------------- flaschengeist/modules/auth_plain/__init__.py | 10 +- .../controller/mainController/__init__.py | 6 +- .../mainController/mainUserController.py | 175 ----------------- .../system/controller/userController.py | 40 ++++ 9 files changed, 115 insertions(+), 351 deletions(-) delete mode 100644 flaschengeist/system/controller/mainController/mainUserController.py create mode 100644 flaschengeist/system/controller/userController.py diff --git a/flaschengeist/__init__.py b/flaschengeist/__init__.py index 3b531c7..7e5dd23 100644 --- a/flaschengeist/__init__.py +++ b/flaschengeist/__init__.py @@ -69,7 +69,7 @@ def create_app(): for entry_point in pkg_resources.iter_entry_points('flaschengeist.plugin'): logger.debug("Found plugin: %s", entry_point.name) if config.get(entry_point.name, 'enabled', fallback=False): - logger.info('Loaded plugin > %s <', entry_point.name) + logger.info("Loaded plugin >{}<".format(entry_point.name)) app.register_blueprint(entry_point.load()()) @app.errorhandler(Exception) @@ -81,4 +81,4 @@ def create_app(): logger.error(str(e), exc_info=True) return jsonify({"error": "Internal server error occurred"}), 500 - return app \ No newline at end of file + return app diff --git a/flaschengeist/flaschengeist.example.cfg b/flaschengeist/flaschengeist.example.cfg index ce5bfde..1312331 100644 --- a/flaschengeist/flaschengeist.example.cfg +++ b/flaschengeist/flaschengeist.example.cfg @@ -9,7 +9,7 @@ AUTH = auth_plain [DATABASE] USER = HOST = -PASSWD = +PASSWORD = DATABASE = # [LDAP] diff --git a/flaschengeist/modules/__init__.py b/flaschengeist/modules/__init__.py index c7cd619..0cdd4bb 100644 --- a/flaschengeist/modules/__init__.py +++ b/flaschengeist/modules/__init__.py @@ -3,29 +3,34 @@ class Auth: pass def login(self, user, pw): - """ - user User class containing at least the uid - pw given password + """ Login routine, MUST BE IMPLEMENTED! - MUST BE IMPLEMENTED! - - should return False if not found or invalid credentials - should return True if success + Args: + user: User class containing at least the uid + pw: given password + Returns: + Must return False if not found or invalid credentials, True if success """ raise NotImplementedError def update_user(self, user): - """ - user User class + """If backend is using external data, then update this user instance with external data - If backend is using external data, then update this user instance with external data + Args: + user: User object """ pass - def modify_user(self, user): - """ - user User class + 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. + Args: + user: User object + password: Password (some backends need the current password for changes) + new_password: If set a password change is requested + Raises: + NotImplemented: If backend does not support this feature (or no password change) + BadRequest: Logic error, e.g. password is wrong. + Error: Other errors if backend went mad (are not handled and will result in a 500 error) """ - pass + raise NotImplemented diff --git a/flaschengeist/modules/auth/__init__.py b/flaschengeist/modules/auth/__init__.py index 1152ffc..db1ae9a 100644 --- a/flaschengeist/modules/auth/__init__.py +++ b/flaschengeist/modules/auth/__init__.py @@ -5,15 +5,14 @@ ############################################# from flask import Blueprint, request, jsonify -from werkzeug.exceptions import Forbidden, BadRequest +from werkzeug.exceptions import Forbidden, BadRequest, Unauthorized from werkzeug.local import LocalProxy from flaschengeist import logger from flaschengeist.system.decorator import login_required -from flaschengeist.system.controller import mainController as mc -import flaschengeist.system.controller.accessTokenController as ac +from flaschengeist.system.controller import accessTokenController, userController -access_controller = LocalProxy(lambda: ac.AccessTokenController()) +access_controller = LocalProxy(lambda: accessTokenController.AccessTokenController()) auth_bp = Blueprint('auth', __name__) @@ -46,8 +45,9 @@ def _create_token(): password = data['password'] logger.debug("search user {{ {} }} in database".format(username)) - main_controller = mc.MainController() - user = main_controller.login_user(username, password) + user = userController.login_user(username, password) + if not user: + raise Unauthorized logger.debug("user is {{ {} }}".format(user)) token = access_controller.create(user, user_agent=request.user_agent) logger.debug("access token is {{ {} }}".format(token)) diff --git a/flaschengeist/modules/auth_ldap/__init__.py b/flaschengeist/modules/auth_ldap/__init__.py index d8b0e70..cc3aa4a 100644 --- a/flaschengeist/modules/auth_ldap/__init__.py +++ b/flaschengeist/modules/auth_ldap/__init__.py @@ -1,18 +1,23 @@ -from ldap3.core.exceptions import LDAPException +from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError +from ldap3.utils.hashed import hashed +from werkzeug.exceptions import BadRequest import flaschengeist.modules as modules -from flaschengeist import logger from flask import current_app as app from flask_ldapconn import LDAPConn -from ldap3 import SUBTREE +from ldap3 import SUBTREE, MODIFY_REPLACE, HASHED_SALTED_SHA512 import ssl +from flaschengeist.system.models.user import User + class AuthLDAP(modules.Auth): _default = { 'PORT': '389', 'USE_SSL': 'False' } + ldap = None + dn = None def configure(self, config): for name in self._default: @@ -31,81 +36,13 @@ class AuthLDAP(modules.Auth): ) if 'SECRET' in config: app.config['LDAP_SECRET'] = config['SECRET'], - self.ldap = LDAPConn(app) self.dn = config['BASEDN'] def login(self, user, password): if not user: return False - try: - r = self.ldap.authenticate(user.uid, password, 'uid', self.dn) - return r == True - except LDAPException as err: - logger.warning("Exception while login into ldap", exc_info=True) - return False - - def modify_user(self, user): - try: - ldap_conn = self.ldap.bind(user.uid, password) - if attributes: - if 'username' in attributes: - debug.debug("change username, so change first in database") - db.changeUsername(user, attributes['username']) - ldap.modifyUser(user, ldap_conn, attributes) - if 'username' in attributes: - retVal = self.getUser(attributes['username']) - debug.debug("user is {{ {} }}".format(retVal)) - return retVal - else: - retVal = self.getUser(user.uid) - debug.debug("user is {{ {} }}".format(retVal)) - return retVal - return self.getUser(user.uid) - - except UsernameExistLDAP as err: - debug.debug( - "username exists on ldap, rechange username on database", exc_info=True) - db.changeUsername(user, user.uid) - raise Exception(err) - except LDAPException as err: - if 'username' in attributes: - db.changeUsername(user, user.uid) - raise Exception(err) - except LDAPPasswordIsMandatoryError as err: - raise Exception('Password wurde nicht gesetzt!!') - except LDAPBindError as err: - raise Exception('Password ist falsch') - except Exception as err: - raise Exception(err) - debug.info( - "modify ldap data from user {{ {} }} with attributes (can't show because here can be a password)".format( - user)) - try: - if 'username' in attributes: - debug.debug("change username") - conn.search('ou=user,{}'.format(self.dn), '(uid={})'.format(attributes['username'])) - if conn.entries: - debug.warning("username already exists", exc_info=True) - raise UsernameExistLDAP("Username already exists in LDAP") - # create modifyer - mody = {} - if 'username' in attributes: - mody['uid'] = [(MODIFY_REPLACE, [attributes['username']])] - if 'firstname' in attributes: - mody['givenName'] = [(MODIFY_REPLACE, [attributes['firstname']])] - if 'lastname' in attributes: - mody['sn'] = [(MODIFY_REPLACE, [attributes['lastname']])] - if 'mail' in attributes: - mody['mail'] = [(MODIFY_REPLACE, [attributes['mail']])] - if 'password' in attributes: - salted_password = hashed(HASHED_SALTED_MD5, attributes['password']) - mody['userPassword'] = [(MODIFY_REPLACE, [salted_password])] - debug.debug("modyfier are (can't show because here can be a password)") - conn.modify(user.dn, mody) - except Exception as err: - debug.warning("exception in modify user data from ldap", exc_info=True) - raise LDAPExcetpion("Something went wrong in LDAP: {}".format(err)) + return self.ldap.authenticate(user.uid, password, 'uid', self.dn) def update_user(self, user): self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(user.uid), SUBTREE, @@ -123,80 +60,37 @@ class AuthLDAP(modules.Auth): user.add_group(group) def _get_groups(self, uid): - try: - groups = [] + groups = [] - self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(uid), SUBTREE, - attributes=['gidNumber']) - 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, + self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(uid), SUBTREE, + attributes=['gidNumber']) + 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_data = self.ldap.connection.response - for data in groups_data: - groups.append(data['attributes']['cn'][0]) - return groups - except Exception as err: - logger.warning("exception in get groups from ldap", exc_info=True) - return [] + groups.append(self.ldap.connection.response[0]['attributes']['cn'][0]) -# def getAllUser(self): -# debug.info("get all users from ldap") -# retVal = [] -# self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid=*)', SUBTREE, attributes=['uid', 'givenName', 'sn', 'mail']) -# data = self.ldap.connection.response -# debug.debug("data is {{ {} }}".format(data)) -# for user in data: -# if 'uid' in user['attributes']: -# username = user['attributes']['uid'][0] -# firstname = user['attributes']['givenName'][0] -# lastname = user['attributes']['sn'][0] -# retVal.append({'username': username, 'firstname': firstname, 'lastname': lastname}) -# debug.debug("users are {{ {} }}".format(retVal)) -# return retVal + self.ldap.connection.search('ou=group,{}'.format(self.dn), '(memberUID={})'.format(uid), SUBTREE, + attributes=['cn']) + groups_data = self.ldap.connection.response + for data in groups_data: + groups.append(data['attributes']['cn'][0]) + return groups -# def searchUser(self, searchString): -# -# name = searchString.split(" ") -# -# for i in range(len(name)): -# name[i] = "*"+name[i]+"*" -# -# -# print(name) -# -# name_result = [] -# -# if len(name) == 1: -# if name[0] == "**": -# self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid=*)', SUBTREE, -# attributes=['uid', 'givenName', 'sn']) -# name_result.append(self.ldap.connection.response) -# else: -# self.ldap.connection.search('ou=user,{}'.format(self.dn), '(givenName={})'.format(name[0]), SUBTREE, attributes=['uid', 'givenName', 'sn', 'mail']) -# name_result.append(self.ldap.connection.response) -# self.ldap.connection.search('ou=user,{}'.format(self.dn), '(sn={})'.format(name[0]), SUBTREE, attributes=['uid', 'givenName', 'sn', 'mail']) -# name_result.append(self.ldap.connection.response) -# else: -# self.ldap.connection.search('ou=user,{}'.format(self.dn), '(givenName={})'.format(name[1]), SUBTREE, attributes=['uid', 'givenName', 'sn']) -# name_result.append(self.ldap.connection.response) -# self.ldap.connection.search('ou=user,{}'.format(self.dn), '(sn={})'.format(name[1]), SUBTREE, attributes=['uid', 'givenName', 'sn', 'mail']) -# name_result.append(self.ldap.connection.response) -# retVal = [] + def modify_user(self, user: User, password, new_password=None): + try: + ldap_conn = self.ldap.connect(user.uid, password) + modifier = {'givenName': [(MODIFY_REPLACE, [user.firstname])], + 'sn': [(MODIFY_REPLACE, [user.lastname])], + 'mail': [(MODIFY_REPLACE, [user.mail])], + 'displayName': [(MODIFY_REPLACE, [user.display_name])], + } + if new_password: + salted_password = hashed(HASHED_SALTED_SHA512, new_password) + modifier['userPassword'] = [(MODIFY_REPLACE, [salted_password])] + ldap_conn.modify(user.dn, modifier) -# for names in name_result: -# for user in names: -# if 'uid' in user['attributes']: -# username = user['attributes']['uid'][0] -# if not self.__isUserInList(retVal, username): -# firstname = user['attributes']['givenName'][0] -# lastname = user['attributes']['sn'][0] -# retVal.append({'username': username, 'firstname': firstname, 'lastname': lastname}) -# -# return retVal + except (LDAPPasswordIsMandatoryError, LDAPBindError): + raise BadRequest diff --git a/flaschengeist/modules/auth_plain/__init__.py b/flaschengeist/modules/auth_plain/__init__.py index 768b127..12ee1da 100644 --- a/flaschengeist/modules/auth_plain/__init__.py +++ b/flaschengeist/modules/auth_plain/__init__.py @@ -3,6 +3,7 @@ import hashlib import os import flaschengeist.modules as modules +from flaschengeist.system.models.user import User def _hash_password(password): @@ -21,9 +22,10 @@ def _verify_password(stored_password, provided_password): class AuthPlain(modules.Auth): - def login(self, user, password): - if not user: - return False - if 'password' in user.attributes: + def login(self, user: User, password: str): + if user and 'password' in user.attributes: return _verify_password(user.attributes['password'].value, password) return False + + def modify_user(self, user, password, new_password=None): + pass diff --git a/flaschengeist/system/controller/mainController/__init__.py b/flaschengeist/system/controller/mainController/__init__.py index 8c098e9..72c0076 100644 --- a/flaschengeist/system/controller/mainController/__init__.py +++ b/flaschengeist/system/controller/mainController/__init__.py @@ -1,7 +1,6 @@ -from .. import Singleton +from .. import Singleton, userController from ...models.user import User from datetime import datetime, timedelta -from . import mainUserController from ...database import db from flask import current_app from werkzeug.local import LocalProxy @@ -10,7 +9,6 @@ logger = LocalProxy(lambda: current_app.logger) class MainController(#mainJobKindController.Base, #mainCreditListController.Base, #mainPricelistController.Base, - mainUserController.Base, #mainWorkerController.Base, #mainWorkgroupController.Base, #mainJobInviteController.Base, @@ -134,7 +132,7 @@ class MainController(#mainJobKindController.Base, return retVal def sendAllMail(self): - debug.info("send mail to all user") + debug.info("send mail to all users") retVal = [] users = db.getAllUser() debug.debug("users are {{ {} }}".format(users)) diff --git a/flaschengeist/system/controller/mainController/mainUserController.py b/flaschengeist/system/controller/mainController/mainUserController.py deleted file mode 100644 index 9ee4246..0000000 --- a/flaschengeist/system/controller/mainController/mainUserController.py +++ /dev/null @@ -1,175 +0,0 @@ -from flask import current_app - -from flaschengeist.system.exceptions import PermissionDenied -from flaschengeist.system.models.user import User -from flaschengeist.system.database import db -from flaschengeist import logger - - -class Base: - def login_user(self, username, password): - logger.info("login user {{ {} }}".format(username)) - user = User.query.filter_by(uid=username).first() - if user is None: - user = User(uid=username) - if current_app.config['FG_AUTH_BACKEND'].login(user, password): - db.session.add(user) - current_app.config['FG_AUTH_BACKEND'].update_user(user) - db.session.commit() - return user - raise PermissionDenied() - - #def getAllStatus(self): - #debug.info("get all status for user") - #retVal = db.getAllStatus() - #debug.debug("status are {{ {} }}".format(retVal)) - #return retVal - - #def getStatus(self, name): - #debug.info("get status of user {{ {} }}".format(name)) - #retVal = db.getStatus(name) - #debug.debug("status of user {{ {} }} is {{ {} }}".format(name, retVal)) - #return retVal - - #def setStatus(self, name): - #debug.info("set status of user {{ {} }}".format(name)) - #retVal = db.setStatus(name) - #debug.debug( - #"settet status of user {{ {} }} is {{ {} }}".format(name, retVal)) - #return retVal - - #def deleteStatus(self, status): - #debug.info("delete status {{ {} }}".format(status)) - #db.deleteStatus(status) - - #def updateStatus(self, status): - #debug.info("update status {{ {} }}".format(status)) - #retVal = db.updateStatus(status) - #debug.debug("updated status is {{ {} }}".format(retVal)) - #return retVal - - #def updateStatusOfUser(self, username, status): - #debug.info("update status {{ {} }} of user {{ {} }}".format( - #status, username)) - #retVal = db.updateStatusOfUser(username, status) - #debug.debug( - #"updatet status of user {{ {} }} is {{ {} }}".format(username, retVal)) - #return retVal - - #def updateVotingOfUser(self, username, voting): - #debug.info("update voting {{ {} }} of user {{ {} }}".format( - #voting, username)) - #retVal = db.updateVotingOfUser(username, voting) - #debug.debug( - #"updatet voting of user {{ {} }} is {{ {} }}".format(username, retVal)) - #return retVal - - #def lockUser(self, username, locked): - #debug.info("lock user {{ {} }} for credit with status {{ {} }}".format( - #username, locked)) - #user = self.getUser(username) - #debug.debug("user is {{ {} }}".format(user)) - #user.updateData({'locked': locked}) - #db.updateUser(user) - #retVal = self.getUser(username) - #debug.debug("locked user is {{ {} }}".format(retVal)) - #return retVal - - #def updateConfig(self, username, data): - #debug.info( - #"update config of user {{ {} }} with config {{ {} }}".format(username, data)) - #user = self.getUser(username) - #debug.debug("user is {{ {} }}".format(user)) - #user.updateData(data) - #db.updateUser(user) - #retVal = self.getUser(username) - #debug.debug("updated config of user is {{ {} }}".format(retVal)) - #return retVal - - #def syncLdap(self): - #debug.info('sync Users from Ldap') - #ldap_users = ldap.getAllUser() - #for user in ldap_users: - #self.getUser(user['username']) - - #def getAllUsersfromDB(self, extern=True): - #debug.info("get all users from database") - #if (len(ldap.getAllUser()) != len(db.getAllUser())): - #self.syncLdap() - #users = db.getAllUser() - #debug.debug("users are {{ {} }}".format(users)) - #for user in users: - #try: - #debug.debug("update data from ldap") - #self.__updateDataFromLDAP(user) - #except: - #pass - #debug.debug("update creditlists") - #self.__updateGeruechte(user) - #retVal = db.getAllUser(extern=extern) - #debug.debug("all users are {{ {} }}".format(retVal)) - #return retVal - - #def getUser(self, username): - #debug.info("get user {{ {} }}".format(username)) - #user = db.getUser(username) - #debug.debug("user is {{ {} }}".format(user)) - #groups = ldap.getGroup(username) - #debug.debug("groups are {{ {} }}".format(groups)) - #user_data = ldap.getUserData(username) - #debug.debug("user data from ldap is {{ {} }}".format(user_data)) - #user_data['gruppe'] = groups - #user_data['group'] = groups - #if user is None: - #debug.debug("user not exists in database -> insert into database") - #user = User(user_data) - #db.insertUser(user) - #else: - #debug.debug("update database with user") - #user.updateData(user_data) - #db.updateUser(user) - #user = db.getUser(username) - #self.__updateGeruechte(user) - #debug.debug("user is {{ {} }}".format(user)) - #return user - - #def modifyUser(self, user, attributes, password): - #debug.info("modify user {{ {} }} with attributes (can't show because here can be a password)".format( - #user)) - - #try: - #ldap_conn = ldap.bind(user, password) - #if attributes: - #if 'username' in attributes: - #debug.debug("change username, so change first in database") - #db.changeUsername(user, attributes['username']) - #ldap.modifyUser(user, ldap_conn, attributes) - #if 'username' in attributes: - #retVal = self.getUser(attributes['username']) - #debug.debug("user is {{ {} }}".format(retVal)) - #return retVal - #else: - #retVal = self.getUser(user.uid) - #debug.debug("user is {{ {} }}".format(retVal)) - #return retVal - #return self.getUser(user.uid) - - #except UsernameExistLDAP as err: - #debug.debug( - #"username exists on ldap, rechange username on database", exc_info=True) - #db.changeUsername(user, user.uid) - #raise Exception(err) - #except LDAPExcetpion as err: - #if 'username' in attributes: - #db.changeUsername(user, user.uid) - #raise Exception(err) - #except LDAPPasswordIsMandatoryError as err: - #raise Exception('Password wurde nicht gesetzt!!') - #except LDAPBindError as err: - #raise Exception('Password ist falsch') - #except Exception as err: - #raise Exception(err) - - #def validateUser(self, username, password): - #debug.info("validate user {{ {} }}".format(username)) - #ldap.login(username, password) diff --git a/flaschengeist/system/controller/userController.py b/flaschengeist/system/controller/userController.py new file mode 100644 index 0000000..79a2e47 --- /dev/null +++ b/flaschengeist/system/controller/userController.py @@ -0,0 +1,40 @@ +from flask import current_app + +from flaschengeist.system.models.user import User +from flaschengeist.system.database import db +from flaschengeist import logger + + +def login_user(username, password): + logger.info("login user {{ {} }}".format(username)) + user = User.query.filter_by(uid=username).one_or_none() + if user is None: + user = User(uid=username) + db.session.add(user) + if current_app.config['FG_AUTH_BACKEND'].login(user, password): + current_app.config['FG_AUTH_BACKEND'].update_user(user) + db.session.commit() + return user + + +def modify_user(user, password, new_password=None): + """Modify given user on the backend + + Args: + user: User object to sync with backend + password: Current password (most backends are needing this) + new_password (optional): New password, if password should be changed + + Raises: + NotImplemented: If backend is not capable of this operation + BadRequest: Password is wrong or other logic issues + """ + current_app.config['FG_AUTH_BACKEND'].modify_user(user, password, new_password) + + +def get_users(): + return User.query.all() + + +def get_user(uid): + return User.query.filter(User.uid == uid).one_or_none()