Some more cleanup, added modify_user to LDAP

This commit is contained in:
Ferdinand Thiessen 2020-09-04 00:55:23 +02:00
parent 7fbff30214
commit 365677697d
9 changed files with 115 additions and 351 deletions

View File

@ -69,7 +69,7 @@ def create_app():
for entry_point in pkg_resources.iter_entry_points('flaschengeist.plugin'): for entry_point in pkg_resources.iter_entry_points('flaschengeist.plugin'):
logger.debug("Found plugin: %s", entry_point.name) logger.debug("Found plugin: %s", entry_point.name)
if config.get(entry_point.name, 'enabled', fallback=False): 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.register_blueprint(entry_point.load()())
@app.errorhandler(Exception) @app.errorhandler(Exception)
@ -81,4 +81,4 @@ def create_app():
logger.error(str(e), exc_info=True) logger.error(str(e), exc_info=True)
return jsonify({"error": "Internal server error occurred"}), 500 return jsonify({"error": "Internal server error occurred"}), 500
return app return app

View File

@ -9,7 +9,7 @@ AUTH = auth_plain
[DATABASE] [DATABASE]
USER = USER =
HOST = HOST =
PASSWD = PASSWORD =
DATABASE = DATABASE =
# [LDAP] # [LDAP]

View File

@ -3,29 +3,34 @@ class Auth:
pass pass
def login(self, user, pw): def login(self, user, pw):
""" """ Login routine, MUST BE IMPLEMENTED!
user User class containing at least the uid
pw given password
MUST BE IMPLEMENTED! Args:
user: User class containing at least the uid
should return False if not found or invalid credentials pw: given password
should return True if success Returns:
Must return False if not found or invalid credentials, True if success
""" """
raise NotImplementedError raise NotImplementedError
def update_user(self, user): def update_user(self, user):
""" """If backend is using external data, then update this user instance with external data
user User class
If backend is using external data, then update this user instance with external data Args:
user: User object
""" """
pass pass
def modify_user(self, user): 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 User class
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

View File

@ -5,15 +5,14 @@
############################################# #############################################
from flask import Blueprint, request, jsonify 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 werkzeug.local import LocalProxy
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.system.decorator import login_required from flaschengeist.system.decorator import login_required
from flaschengeist.system.controller import mainController as mc from flaschengeist.system.controller import accessTokenController, userController
import flaschengeist.system.controller.accessTokenController as ac
access_controller = LocalProxy(lambda: ac.AccessTokenController()) access_controller = LocalProxy(lambda: accessTokenController.AccessTokenController())
auth_bp = Blueprint('auth', __name__) auth_bp = Blueprint('auth', __name__)
@ -46,8 +45,9 @@ def _create_token():
password = data['password'] password = data['password']
logger.debug("search user {{ {} }} in database".format(username)) logger.debug("search user {{ {} }} in database".format(username))
main_controller = mc.MainController() user = userController.login_user(username, password)
user = main_controller.login_user(username, password) if not user:
raise Unauthorized
logger.debug("user is {{ {} }}".format(user)) logger.debug("user is {{ {} }}".format(user))
token = access_controller.create(user, user_agent=request.user_agent) token = access_controller.create(user, user_agent=request.user_agent)
logger.debug("access token is {{ {} }}".format(token)) logger.debug("access token is {{ {} }}".format(token))

View File

@ -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 import flaschengeist.modules as modules
from flaschengeist import logger
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 ldap3 import SUBTREE from ldap3 import SUBTREE, MODIFY_REPLACE, HASHED_SALTED_SHA512
import ssl import ssl
from flaschengeist.system.models.user import User
class AuthLDAP(modules.Auth): class AuthLDAP(modules.Auth):
_default = { _default = {
'PORT': '389', 'PORT': '389',
'USE_SSL': 'False' 'USE_SSL': 'False'
} }
ldap = None
dn = None
def configure(self, config): def configure(self, config):
for name in self._default: for name in self._default:
@ -31,81 +36,13 @@ class AuthLDAP(modules.Auth):
) )
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['BASEDN']
def login(self, user, password): def login(self, user, password):
if not user: if not user:
return False return False
try: return self.ldap.authenticate(user.uid, password, 'uid', self.dn)
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))
def update_user(self, user): def update_user(self, user):
self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(user.uid), SUBTREE, 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) user.add_group(group)
def _get_groups(self, uid): def _get_groups(self, uid):
try: groups = []
groups = []
self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(uid), SUBTREE, self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(uid), SUBTREE,
attributes=['gidNumber']) attributes=['gidNumber'])
main_group_number = self.ldap.connection.response[0]['attributes']['gidNumber'] main_group_number = self.ldap.connection.response[0]['attributes']['gidNumber']
if main_group_number: if main_group_number:
if type(main_group_number) is list: if type(main_group_number) is list:
main_group_number = main_group_number[0] main_group_number = main_group_number[0]
self.ldap.connection.search('ou=group,{}'.format(self.dn), '(gidNumber={})'.format(main_group_number), 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']) attributes=['cn'])
groups_data = self.ldap.connection.response groups.append(self.ldap.connection.response[0]['attributes']['cn'][0])
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 []
# def getAllUser(self): self.ldap.connection.search('ou=group,{}'.format(self.dn), '(memberUID={})'.format(uid), SUBTREE,
# debug.info("get all users from ldap") attributes=['cn'])
# retVal = [] groups_data = self.ldap.connection.response
# self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid=*)', SUBTREE, attributes=['uid', 'givenName', 'sn', 'mail']) for data in groups_data:
# data = self.ldap.connection.response groups.append(data['attributes']['cn'][0])
# debug.debug("data is {{ {} }}".format(data)) return groups
# 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
# def searchUser(self, searchString): def modify_user(self, user: User, password, new_password=None):
# try:
# name = searchString.split(" ") ldap_conn = self.ldap.connect(user.uid, password)
# modifier = {'givenName': [(MODIFY_REPLACE, [user.firstname])],
# for i in range(len(name)): 'sn': [(MODIFY_REPLACE, [user.lastname])],
# name[i] = "*"+name[i]+"*" 'mail': [(MODIFY_REPLACE, [user.mail])],
# 'displayName': [(MODIFY_REPLACE, [user.display_name])],
# }
# print(name) if new_password:
# salted_password = hashed(HASHED_SALTED_SHA512, new_password)
# name_result = [] modifier['userPassword'] = [(MODIFY_REPLACE, [salted_password])]
# ldap_conn.modify(user.dn, modifier)
# 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 = []
# for names in name_result: except (LDAPPasswordIsMandatoryError, LDAPBindError):
# for user in names: raise BadRequest
# 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

View File

@ -3,6 +3,7 @@ import hashlib
import os import os
import flaschengeist.modules as modules import flaschengeist.modules as modules
from flaschengeist.system.models.user import User
def _hash_password(password): def _hash_password(password):
@ -21,9 +22,10 @@ def _verify_password(stored_password, provided_password):
class AuthPlain(modules.Auth): class AuthPlain(modules.Auth):
def login(self, user, password): def login(self, user: User, password: str):
if not user: if user and 'password' in user.attributes:
return False
if 'password' in user.attributes:
return _verify_password(user.attributes['password'].value, password) return _verify_password(user.attributes['password'].value, password)
return False return False
def modify_user(self, user, password, new_password=None):
pass

View File

@ -1,7 +1,6 @@
from .. import Singleton from .. import Singleton, userController
from ...models.user import User from ...models.user import User
from datetime import datetime, timedelta from datetime import datetime, timedelta
from . import mainUserController
from ...database import db from ...database import db
from flask import current_app from flask import current_app
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
@ -10,7 +9,6 @@ logger = LocalProxy(lambda: current_app.logger)
class MainController(#mainJobKindController.Base, class MainController(#mainJobKindController.Base,
#mainCreditListController.Base, #mainCreditListController.Base,
#mainPricelistController.Base, #mainPricelistController.Base,
mainUserController.Base,
#mainWorkerController.Base, #mainWorkerController.Base,
#mainWorkgroupController.Base, #mainWorkgroupController.Base,
#mainJobInviteController.Base, #mainJobInviteController.Base,
@ -134,7 +132,7 @@ class MainController(#mainJobKindController.Base,
return retVal return retVal
def sendAllMail(self): def sendAllMail(self):
debug.info("send mail to all user") debug.info("send mail to all users")
retVal = [] retVal = []
users = db.getAllUser() users = db.getAllUser()
debug.debug("users are {{ {} }}".format(users)) debug.debug("users are {{ {} }}".format(users))

View File

@ -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)

View File

@ -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()