diff --git a/flaschengeist/__init__.py b/flaschengeist/__init__.py index 0ea570e..56a8348 100644 --- a/flaschengeist/__init__.py +++ b/flaschengeist/__init__.py @@ -1,26 +1,25 @@ """ Server-package - Initialize app, cors, database and bcrypt (for passwordhashing) and added it to the application. - Initialize also a singelton for the AccesTokenControler and start the Thread. + Initialize app, CORS, database and add it to the application. + Initialize also a singleton for the AccessTokenController and start the Thread. """ -from pathlib import Path -_modpath = Path(__file__).parent - -from flask import Flask -from flask_cors import CORS -import pkg_resources, yaml - -from logging.config import dictConfig - -with (_modpath/'logging.yml').open(mode='rb') as file: - config = yaml.safe_load(file.read()) - dictConfig(config) - +import yaml import logging +import pkg_resources +from flask import Flask +from pathlib import Path +from flask_cors import CORS +from logging.config import dictConfig from werkzeug.local import LocalProxy + +_module_path = Path(__file__).parent logger = LocalProxy(lambda: logging.getLogger(__name__)) +with (_module_path / 'logging.yml').open(mode='rb') as file: + config = yaml.safe_load(file.read()) + logging.config.dictConfig(config) + def create_app(): app = Flask(__name__) @@ -33,14 +32,15 @@ def create_app(): db.init_app(app) for entry_point in pkg_resources.iter_entry_points('flaschengeist.auth'): - logger.debug('Found authentification plugin: %s', entry_point.name) + logger.debug('Found authentication plugin: %s', entry_point.name) if entry_point.name == config['FLASCHENGEIST']['AUTH']: app.config['FG_AUTH_BACKEND'] = entry_point.load()() - app.config['FG_AUTH_BACKEND'].configure(config[entry_point.name] if config.has_section(entry_point.name) else {}) - logger.info('Loaded authentification plugin > %s <', entry_point.name) + app.config['FG_AUTH_BACKEND'].configure( + config[entry_point.name] if config.has_section(entry_point.name) else {}) + logger.info('Loaded authentication plugin > %s <', entry_point.name) break if not app.config['FG_AUTH_BACKEND']: - logger.error('No authentification plugin configured or authentification plugin not found') + logger.error('No authentication plugin configured or authentication plugin not found') logger.info('Search for plugins') for entry_point in pkg_resources.iter_entry_points('flaschengeist.plugin'): @@ -50,9 +50,3 @@ def create_app(): app.register_blueprint(entry_point.load()()) return app -#app.register_blueprint(baruser) -#app.register_blueprint(finanzer) -#app.register_blueprint(user) -#app.register_blueprint(vorstand) -#app.register_blueprint(gastrouser) -#app.register_blueprint(registration) diff --git a/flaschengeist/flaschengeist.example.cfg b/flaschengeist/flaschengeist.example.cfg index 1631a1c..ce5bfde 100644 --- a/flaschengeist/flaschengeist.example.cfg +++ b/flaschengeist/flaschengeist.example.cfg @@ -1,8 +1,10 @@ [FLASCHENGEIST] -# Set lifetime of session (idle time until you get logged out) -AccessTokenLifetime = 1800 -# Select authentification provider +# Select authentication provider (builtin: auth_plain, auth_ldap) AUTH = auth_plain +# Enable if you run flaschengeist behind a proxy, e.g. nginx + gunicorn +# PROXY = false +# Set root path, prefixes all routes +# ROOT = / [DATABASE] USER = diff --git a/flaschengeist/modules/__init__.py b/flaschengeist/modules/__init__.py index a985443..c7cd619 100644 --- a/flaschengeist/modules/__init__.py +++ b/flaschengeist/modules/__init__.py @@ -1,4 +1,4 @@ -class Auth(): +class Auth: def configure(self, config): pass @@ -7,14 +7,14 @@ class Auth(): user User class containing at least the uid pw given password - HAS TO BE IMPLEMENTED! + MUST BE IMPLEMENTED! should return False if not found or invalid credentials should return True if success """ - return False + raise NotImplementedError - def updateUser(self, user): + def update_user(self, user): """ user User class @@ -22,11 +22,10 @@ class Auth(): """ pass - def modifyUser(self, user): + def modify_user(self, user): """ user User class If backend is using (writeable) external data, then update the external database with the user provided. """ pass - diff --git a/flaschengeist/modules/auth/__init__.py b/flaschengeist/modules/auth/__init__.py index 1426083..b9c5b34 100644 --- a/flaschengeist/modules/auth/__init__.py +++ b/flaschengeist/modules/auth/__init__.py @@ -45,7 +45,7 @@ def _login(): try: logger.debug("search {{ {} }} in database".format(username)) main_controller = mc.MainController() - user = main_controller.loginUser(username, password) + user = main_controller.login_user(username, password) 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 1eb1530..d8b0e70 100644 --- a/flaschengeist/modules/auth_ldap/__init__.py +++ b/flaschengeist/modules/auth_ldap/__init__.py @@ -1,3 +1,5 @@ +from ldap3.core.exceptions import LDAPException + import flaschengeist.modules as modules from flaschengeist import logger from flask import current_app as app @@ -39,11 +41,11 @@ class AuthLDAP(modules.Auth): try: r = self.ldap.authenticate(user.uid, password, 'uid', self.dn) return r == True - except Exception as err: + except LDAPException as err: logger.warning("Exception while login into ldap", exc_info=True) return False - def modifyUser(self, user): + def modify_user(self, user): try: ldap_conn = self.ldap.bind(user.uid, password) if attributes: @@ -66,7 +68,7 @@ class AuthLDAP(modules.Auth): "username exists on ldap, rechange username on database", exc_info=True) db.changeUsername(user, user.uid) raise Exception(err) - except LDAPExcetpion as err: + except LDAPException as err: if 'username' in attributes: db.changeUsername(user, user.uid) raise Exception(err) @@ -105,7 +107,7 @@ class AuthLDAP(modules.Auth): debug.warning("exception in modify user data from ldap", exc_info=True) raise LDAPExcetpion("Something went wrong in LDAP: {}".format(err)) - def updateUser(self, user): + def update_user(self, user): self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(user.uid), SUBTREE, attributes=['uid', 'givenName', 'sn', 'mail']) r = self.ldap.connection.response[0]['attributes'] diff --git a/flaschengeist/modules/auth_plain/__init__.py b/flaschengeist/modules/auth_plain/__init__.py index 2677d19..768b127 100644 --- a/flaschengeist/modules/auth_plain/__init__.py +++ b/flaschengeist/modules/auth_plain/__init__.py @@ -5,23 +5,25 @@ import os import flaschengeist.modules as modules +def _hash_password(password): + salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') + pass_hash = hashlib.pbkdf2_hmac('sha3-512', password.encode('utf-8'), salt, 100000) + pass_hash = binascii.hexlify(pass_hash) + return (salt + pass_hash).decode('ascii') + + +def _verify_password(stored_password, provided_password): + salt = stored_password[:64] + stored_password = stored_password[64:] + pass_hash = hashlib.pbkdf2_hmac('sha3-512', provided_password.encode('utf-8'), salt.encode('ascii'), 100000) + pass_hash = binascii.hexlify(pass_hash).decode('ascii') + return pass_hash == stored_password + + class AuthPlain(modules.Auth): def login(self, user, password): if not user: return False if 'password' in user.attributes: - return self._verify_password(user.attributes['password'].value, password) + return _verify_password(user.attributes['password'].value, password) return False - - def _hash_password(self, password): - salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') - pass_hash = hashlib.pbkdf2_hmac('sha3-512', password.encode('utf-8'), salt, 100000) - pass_hash = binascii.hexlify(pass_hash) - return (salt + pass_hash).decode('ascii') - - def _verify_password(self, stored_password, provided_password): - salt = stored_password[:64] - stored_password = stored_password[64:] - pass_hash = hashlib.pbkdf2_hmac('sha3-512', provided_password.encode('utf-8'), salt.encode('ascii'), 100000) - pass_hash = binascii.hexlify(pass_hash).decode('ascii') - return pass_hash == stored_password diff --git a/flaschengeist/system/config.py b/flaschengeist/system/config.py index a83d6c0..b9833ae 100644 --- a/flaschengeist/system/config.py +++ b/flaschengeist/system/config.py @@ -1,12 +1,10 @@ -import configparser import os +import configparser from pathlib import Path -from .. import _modpath, logger +from werkzeug.middleware.proxy_fix import ProxyFix +from .. import _module_path, logger default = { - 'FLASCHENGEIST': { - 'AccessTokenLifeTime': 1800 - }, 'MAIL': { 'CRYPT': 'SSL/STARTLS' } @@ -14,7 +12,7 @@ default = { config = configparser.ConfigParser() config.read_dict(default) -paths = [_modpath, Path.home()/".config"] +paths = [_module_path, Path.home()/".config"] if 'FLASCHENGEIST_CONF' in os.environ: paths.append(Path(os.environ.get("FLASCHENGEIST_CONF"))) for loc in paths: @@ -23,7 +21,8 @@ for loc in paths: config.read_file(source) except IOError: pass -# Always enable this buildin plugins! + +# Always enable this builtin plugins! config.read_dict({ 'auth': { 'enabled': True @@ -43,120 +42,8 @@ def configure_app(app): database=config['DATABASE']['database'] ) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - -#class ConifgParser(): -# def __init__(self, file='config.yml'): -# self.file = file -# with open(file, 'r') as f: -# self.config = yaml.safe_load(f) -# -# if 'Database' not in self.config: -# self.__error__( -# 'Wrong Configuration for Database. You should configure databaseconfig with "URL", "user", "passwd", "database"') -# if 'URL' not in self.config['Database'] or 'user' not in self.config['Database'] or 'passwd' not in self.config['Database'] or 'database' not in self.config['Database']: -# self.__error__( -# 'Wrong Configuration for Database. You should configure databaseconfig with "URL", "user", "passwd", "database"') -# -# self.db = self.config['Database'] -# logger.debug("Set Databaseconfig: {}".format(self.db)) -# -# if 'LDAP' not in self.config: -# self.__error__( -# 'Wrong Configuration for LDAP. You should configure ldapconfig with "URL" and "BIND_DN"') -# if 'URL' not in self.config['LDAP'] or 'DN' not in self.config['LDAP']: -# self.__error__( -# 'Wrong Configuration for LDAP. You should configure ldapconfig with "URL" and "BIND_DN"') -# if 'PORT' not in self.config['LDAP']: -# logger.info( -# 'No Config for port in LDAP found. Set it to default: {}'.format(389)) -# self.config['LDAP']['PORT'] = 389 -# if 'ADMIN_DN' not in self.config['LDAP']: -# logger.info( -# 'No Config for ADMIN_DN in LDAP found. Set it to default {}. (Maybe Password reset not working)'.format(None) -# ) -# self.config['LDAP']['ADMIN_DN'] = None -# if 'ADMIN_SECRET' not in self.config['LDAP']: -# logger.info( -# 'No Config for ADMIN_SECRET in LDAP found. Set it to default {}. (Maybe Password reset not working)'.format(None) -# ) -# self.config['LDAP']['ADMIN_SECRET'] = None -# if 'USER_DN' not in self.config['LDAP']: -# logger.info( -# 'No Config for USER_DN in LDAP found. Set it to default {}. (Maybe Password reset not working)'.format(None) -# ) -# self.config['LDAP']['USER_DN'] = None -# if 'BIND_DN' not in self.config['LDAP']: -# logger.info( -# 'No Config for BIND_DN in LDAP found. Set it to default {}. (Maybe Password reset not working)'.format(None) -# ) -# self.config['LDAP']['BIND_DN'] = None -# if 'BIND_SECRET' not in self.config['LDAP']: -# logger.info( -# 'No Config for BIND_SECRET in LDAP found. Set it to default {}. (Maybe Password reset not working)'.format(None) -# ) -# self.config['LDAP']['BIND_SECRET'] = None -# if 'SSL' not in self.config['LDAP']: -# logger.info( -# 'No Config for SSL in LDAP found. Set it to default {}. (Maybe Password reset not working)'.format(False) -# ) -# self.config['LDAP']['SSL'] = False -# else: -# self.config['LDAP']['SSL'] = bool(self.config['LDAP']['SSL']) -# self.ldap = self.config['LDAP'] -# logger.debug("Set LDAPconfig: {}".format(self.ldap)) -# if 'AccessTokenLifeTime' in self.config: -# self.accessTokenLifeTime = int(self.config['AccessTokenLifeTime']) -# logger.info("Set AccessTokenLifeTime: {}".format( -# self.accessTokenLifeTime)) -# else: -# self.accessTokenLifeTime = default['AccessTokenLifeTime'] -# logger.info("No Config for AccessTokenLifetime found. Set it to default: {}".format( -# self.accessTokenLifeTime)) -# -# if 'Mail' not in self.config: -# self.config['Mail'] = default['Mail'] -# logger.info('No Conifg for Mail found. Set it to defaul: {}'.format( -# self.config['Mail'])) -# if 'URL' not in self.config['Mail']: -# self.config['Mail']['URL'] = default['Mail']['URL'] -# logger.info("No Config for URL in Mail found. Set it to default") -# if 'port' not in self.config['Mail']: -# self.config['Mail']['port'] = default['Mail']['port'] -# logger.info("No Config for port in Mail found. Set it to default") -# else: -# self.config['Mail']['port'] = int(self.config['Mail']['port']) -# logger.info("No Conifg for port in Mail found. Set it to default") -# if 'user' not in self.config['Mail']: -# self.config['Mail']['user'] = default['Mail']['user'] -# logger.info("No Config for user in Mail found. Set it to default") -# if 'passwd' not in self.config['Mail']: -# self.config['Mail']['passwd'] = default['Mail']['passwd'] -# logger.info("No Config for passwd in Mail found. Set it to default") -# if 'email' not in self.config['Mail']: -# self.config['Mail']['email'] = default['Mail']['email'] -# logger.info("No Config for email in Mail found. Set it to default") -# if 'crypt' not in self.config['Mail']: -# self.config['Mail']['crypt'] = default['Mail']['crypt'] -# logger.info("No Config for crypt in Mail found. Set it to default") -# self.mail = self.config['Mail'] -# logger.debug('Set Mailconfig: {}'.format(self.mail)) -# -# def getLDAP(self): -# return self.ldap -# -# def getDatabase(self): -# return self.db -# -# def getAccessToken(self): -# return self.accessTokenLifeTime -# -# def getMail(self): -# return self.mail -# -# def __error__(self, msg): -# logger.error(msg, exc_info=True) -# sys.exit(-1) -# -# -#if __name__ == '__main__': -# ConifgParser() + + if config.has_option("FLASCHENGEIST", "ROOT"): + app.config["APPLICATION_ROOT"] = config["FLASCHENGEIST"]["ROOT"] + if config.getboolean("FLASCHENGEIST", "PROXY", fallback=False): + app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) diff --git a/flaschengeist/system/controller/mainController/mainUserController.py b/flaschengeist/system/controller/mainController/mainUserController.py index 4927650..9ee4246 100644 --- a/flaschengeist/system/controller/mainController/mainUserController.py +++ b/flaschengeist/system/controller/mainController/mainUserController.py @@ -7,14 +7,14 @@ from flaschengeist import logger class Base: - def loginUser(self, username, password): + 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'].updateUser(user) + current_app.config['FG_AUTH_BACKEND'].update_user(user) db.session.commit() return user raise PermissionDenied()