This commit is contained in:
Ferdinand Thiessen 2020-09-03 17:56:12 +02:00
parent 5bfa305c41
commit ea107a28dd
8 changed files with 65 additions and 179 deletions

View File

@ -1,26 +1,25 @@
""" Server-package """ Server-package
Initialize app, cors, database and bcrypt (for passwordhashing) and added it to the application. Initialize app, CORS, database and add it to the application.
Initialize also a singelton for the AccesTokenControler and start the Thread. Initialize also a singleton for the AccessTokenController and start the Thread.
""" """
from pathlib import Path import yaml
_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 logging 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 from werkzeug.local import LocalProxy
_module_path = Path(__file__).parent
logger = LocalProxy(lambda: logging.getLogger(__name__)) 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(): def create_app():
app = Flask(__name__) app = Flask(__name__)
@ -33,14 +32,15 @@ def create_app():
db.init_app(app) db.init_app(app)
for entry_point in pkg_resources.iter_entry_points('flaschengeist.auth'): 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']: if entry_point.name == config['FLASCHENGEIST']['AUTH']:
app.config['FG_AUTH_BACKEND'] = entry_point.load()() 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 {}) app.config['FG_AUTH_BACKEND'].configure(
logger.info('Loaded authentification plugin > %s <', entry_point.name) config[entry_point.name] if config.has_section(entry_point.name) else {})
logger.info('Loaded authentication plugin > %s <', entry_point.name)
break break
if not app.config['FG_AUTH_BACKEND']: 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') logger.info('Search for plugins')
for entry_point in pkg_resources.iter_entry_points('flaschengeist.plugin'): for entry_point in pkg_resources.iter_entry_points('flaschengeist.plugin'):
@ -50,9 +50,3 @@ def create_app():
app.register_blueprint(entry_point.load()()) app.register_blueprint(entry_point.load()())
return app 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)

View File

@ -1,8 +1,10 @@
[FLASCHENGEIST] [FLASCHENGEIST]
# Set lifetime of session (idle time until you get logged out) # Select authentication provider (builtin: auth_plain, auth_ldap)
AccessTokenLifetime = 1800
# Select authentification provider
AUTH = auth_plain 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] [DATABASE]
USER = USER =

View File

@ -1,4 +1,4 @@
class Auth(): class Auth:
def configure(self, config): def configure(self, config):
pass pass
@ -7,14 +7,14 @@ class Auth():
user User class containing at least the uid user User class containing at least the uid
pw given password pw given password
HAS TO BE IMPLEMENTED! MUST BE IMPLEMENTED!
should return False if not found or invalid credentials should return False if not found or invalid credentials
should return True if success should return True if success
""" """
return False raise NotImplementedError
def updateUser(self, user): def update_user(self, user):
""" """
user User class user User class
@ -22,11 +22,10 @@ class Auth():
""" """
pass pass
def modifyUser(self, user): def modify_user(self, user):
""" """
user User class user User class
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.
""" """
pass pass

View File

@ -45,7 +45,7 @@ def _login():
try: try:
logger.debug("search {{ {} }} in database".format(username)) logger.debug("search {{ {} }} in database".format(username))
main_controller = mc.MainController() main_controller = mc.MainController()
user = main_controller.loginUser(username, password) user = main_controller.login_user(username, password)
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,3 +1,5 @@
from ldap3.core.exceptions import LDAPException
import flaschengeist.modules as modules import flaschengeist.modules as modules
from flaschengeist import logger from flaschengeist import logger
from flask import current_app as app from flask import current_app as app
@ -39,11 +41,11 @@ class AuthLDAP(modules.Auth):
try: try:
r = self.ldap.authenticate(user.uid, password, 'uid', self.dn) r = self.ldap.authenticate(user.uid, password, 'uid', self.dn)
return r == True return r == True
except Exception as err: except LDAPException as err:
logger.warning("Exception while login into ldap", exc_info=True) logger.warning("Exception while login into ldap", exc_info=True)
return False return False
def modifyUser(self, user): def modify_user(self, user):
try: try:
ldap_conn = self.ldap.bind(user.uid, password) ldap_conn = self.ldap.bind(user.uid, password)
if attributes: if attributes:
@ -66,7 +68,7 @@ class AuthLDAP(modules.Auth):
"username exists on ldap, rechange username on database", exc_info=True) "username exists on ldap, rechange username on database", exc_info=True)
db.changeUsername(user, user.uid) db.changeUsername(user, user.uid)
raise Exception(err) raise Exception(err)
except LDAPExcetpion as err: except LDAPException as err:
if 'username' in attributes: if 'username' in attributes:
db.changeUsername(user, user.uid) db.changeUsername(user, user.uid)
raise Exception(err) raise Exception(err)
@ -105,7 +107,7 @@ class AuthLDAP(modules.Auth):
debug.warning("exception in modify user data from ldap", exc_info=True) debug.warning("exception in modify user data from ldap", exc_info=True)
raise LDAPExcetpion("Something went wrong in LDAP: {}".format(err)) 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, self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(user.uid), SUBTREE,
attributes=['uid', 'givenName', 'sn', 'mail']) attributes=['uid', 'givenName', 'sn', 'mail'])
r = self.ldap.connection.response[0]['attributes'] r = self.ldap.connection.response[0]['attributes']

View File

@ -5,23 +5,25 @@ import os
import flaschengeist.modules as modules 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): class AuthPlain(modules.Auth):
def login(self, user, password): def login(self, user, password):
if not user: if not user:
return False return False
if 'password' in user.attributes: if 'password' in user.attributes:
return self._verify_password(user.attributes['password'].value, password) return _verify_password(user.attributes['password'].value, password)
return False 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

View File

@ -1,12 +1,10 @@
import configparser
import os import os
import configparser
from pathlib import Path from pathlib import Path
from .. import _modpath, logger from werkzeug.middleware.proxy_fix import ProxyFix
from .. import _module_path, logger
default = { default = {
'FLASCHENGEIST': {
'AccessTokenLifeTime': 1800
},
'MAIL': { 'MAIL': {
'CRYPT': 'SSL/STARTLS' 'CRYPT': 'SSL/STARTLS'
} }
@ -14,7 +12,7 @@ default = {
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read_dict(default) config.read_dict(default)
paths = [_modpath, Path.home()/".config"] paths = [_module_path, Path.home()/".config"]
if 'FLASCHENGEIST_CONF' in os.environ: if 'FLASCHENGEIST_CONF' in os.environ:
paths.append(Path(os.environ.get("FLASCHENGEIST_CONF"))) paths.append(Path(os.environ.get("FLASCHENGEIST_CONF")))
for loc in paths: for loc in paths:
@ -23,7 +21,8 @@ for loc in paths:
config.read_file(source) config.read_file(source)
except IOError: except IOError:
pass pass
# Always enable this buildin plugins!
# Always enable this builtin plugins!
config.read_dict({ config.read_dict({
'auth': { 'auth': {
'enabled': True 'enabled': True
@ -43,120 +42,8 @@ def configure_app(app):
database=config['DATABASE']['database'] database=config['DATABASE']['database']
) )
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
#class ConifgParser(): if config.has_option("FLASCHENGEIST", "ROOT"):
# def __init__(self, file='config.yml'): app.config["APPLICATION_ROOT"] = config["FLASCHENGEIST"]["ROOT"]
# self.file = file if config.getboolean("FLASCHENGEIST", "PROXY", fallback=False):
# with open(file, 'r') as f: app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
# 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()

View File

@ -7,14 +7,14 @@ from flaschengeist import logger
class Base: class Base:
def loginUser(self, username, password): def login_user(self, username, password):
logger.info("login user {{ {} }}".format(username)) logger.info("login user {{ {} }}".format(username))
user = User.query.filter_by(uid=username).first() user = User.query.filter_by(uid=username).first()
if user is None: if user is None:
user = User(uid=username) user = User(uid=username)
if current_app.config['FG_AUTH_BACKEND'].login(user, password): if current_app.config['FG_AUTH_BACKEND'].login(user, password):
db.session.add(user) db.session.add(user)
current_app.config['FG_AUTH_BACKEND'].updateUser(user) current_app.config['FG_AUTH_BACKEND'].update_user(user)
db.session.commit() db.session.commit()
return user return user
raise PermissionDenied() raise PermissionDenied()