diff --git a/.gitignore b/.gitignore index 293d4a7..71796df 100644 --- a/.gitignore +++ b/.gitignore @@ -117,8 +117,8 @@ dmypy.json #ide .idea -.swp -.swo +*.swp +*.swo .vscode/ *.log diff --git a/flaschengeist/__init__.py b/flaschengeist/__init__.py index 2cbd85f..1a29c59 100644 --- a/flaschengeist/__init__.py +++ b/flaschengeist/__init__.py @@ -1,5 +1,84 @@ """ 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. + +""" from pathlib import Path _modpath = Path(__file__).parent + +from flask import Flask +from flask_cors import CORS +from flask_ldapconn import LDAPConn +import ssl +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) + +def create_app(): + app = Flask(__name__) + CORS(app) + + with app.app_context(): + from .system.controller import dbConfig, ldapConfig + from .system.database import db + app.config['SECRET_KEY'] = '0a657b97ef546da90b2db91862ad4e29' + + app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://{user}:{passwd}@{host}/{database}'.format( + user=dbConfig['user'], + passwd=dbConfig['passwd'], + host=dbConfig['URL'], + database=dbConfig['database']) + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +# app.config['MYSQL_CURSORCLASS'] = 'DictCursor' + app.config['LDAP_SERVER'] = ldapConfig['URL'] + app.config['LDAP_PORT'] = ldapConfig['PORT'] + if ldapConfig['BIND_DN']: + app.config['LDAP_BINDDN'] = ldapConfig['BIND_DN'] + else: + app.config['LDAP_BINDDN'] = ldapConfig['DN'] + if ldapConfig['BIND_SECRET']: + app.config['LDAP_SECRET'] = ldapConfig['BIND_SECRET'] + app.config['LDAP_USE_TLS'] = False + app.config['LDAP_USE_SSL'] = ldapConfig['SSL'] + app.config['LDAP_TLS_VERSION'] = ssl.PROTOCOL_TLSv1_2 + app.config['LDAP_REQUIRE_CERT'] = ssl.CERT_NONE + app.config['FORCE_ATTRIBUTE_VALUE_AS_LIST'] = True + + app.config['FG_AUTH_BACKENDS'] = [ + entry_point.load() for entry_point in pkg_resources.iter_entry_points('flaschengeist.auth') + ] + + ldap = LDAPConn(app) + db.init_app(app) + + discovered_plugins = { + entry_point.name: entry_point.load() + for entry_point in pkg_resources.iter_entry_points('flaschengeist.plugin') + } + +#from geruecht import routes +#from geruecht.baruser.routes import baruser +#from geruecht.finanzer.routes import finanzer +#from geruecht.user.routes import user +#from geruecht.vorstand.routes import vorstand +#from geruecht.gastro.routes import gastrouser +#from geruecht.registration_route import registration + + app.logger.info("Registrate bluebrints") + for name in discovered_plugins: + app.logger.info("Register plugin: %s" % name) + app.register_blueprint(discovered_plugins[name]()) + + 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/app.py b/flaschengeist/app.py deleted file mode 100644 index a2ec07c..0000000 --- a/flaschengeist/app.py +++ /dev/null @@ -1,72 +0,0 @@ -""" 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. - -""" -from .system.logger import getDebugLogger -from .system.controller import dbConfig, ldapConfig -from flask_mysqldb import MySQL -from flask_ldapconn import LDAPConn -import ssl - -DEBUG = getDebugLogger() -DEBUG.info("Initialize App") - -from flask import Flask -from flask_cors import CORS - -DEBUG.info("Build APP") -app = Flask(__name__) -CORS(app) - -app.config['SECRET_KEY'] = '0a657b97ef546da90b2db91862ad4e29' -app.config['MYSQL_HOST'] = dbConfig['URL'] -app.config['MYSQL_USER'] = dbConfig['user'] -app.config['MYSQL_PASSWORD'] = dbConfig['passwd'] -app.config['MYSQL_DB'] = dbConfig['database'] -app.config['MYSQL_CURSORCLASS'] = 'DictCursor' -app.config['LDAP_SERVER'] = ldapConfig['URL'] -app.config['LDAP_PORT'] = ldapConfig['PORT'] -if ldapConfig['BIND_DN']: - app.config['LDAP_BINDDN'] = ldapConfig['BIND_DN'] -else: - app.config['LDAP_BINDDN'] = ldapConfig['DN'] -if ldapConfig['BIND_SECRET']: - app.config['LDAP_SECRET'] = ldapConfig['BIND_SECRET'] -app.config['LDAP_USE_TLS'] = False -app.config['LDAP_USE_SSL'] = ldapConfig['SSL'] -app.config['LDAP_TLS_VERSION'] = ssl.PROTOCOL_TLSv1_2 -app.config['LDAP_REQUIRE_CERT'] = ssl.CERT_NONE -app.config['FORCE_ATTRIBUTE_VALUE_AS_LIST'] = True - -ldap = LDAPConn(app) -db = MySQL(app) - -import pkg_resources - -discovered_plugins = { - entry_point.name: entry_point.load() - for entry_point - in pkg_resources.iter_entry_points('flaschengeist.plugins') -} - -#from geruecht import routes -#from geruecht.baruser.routes import baruser -#from geruecht.finanzer.routes import finanzer -#from geruecht.user.routes import user -#from geruecht.vorstand.routes import vorstand -#from geruecht.gastro.routes import gastrouser -#from geruecht.registration_route import registration - -DEBUG.info("Registrate bluebrints") -for name in discovered_plugins: - DEBUG.info("Register %s" % name) - app.register_blueprint(discovered_plugins[name]()) - -#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/system/configparser.py b/flaschengeist/system/configparser.py index 1fbe90c..94297e4 100644 --- a/flaschengeist/system/configparser.py +++ b/flaschengeist/system/configparser.py @@ -1,7 +1,8 @@ import yaml import sys -from .logger import getDebugLogger -DEBUG = getDebugLogger() +from flask import current_app +from werkzeug.local import LocalProxy +logger = LocalProxy(lambda: current_app.logger) default = { 'AccessTokenLifeTime': 1800, @@ -30,7 +31,7 @@ class ConifgParser(): 'Wrong Configuration for Database. You should configure databaseconfig with "URL", "user", "passwd", "database"') self.db = self.config['Database'] - DEBUG.debug("Set Databaseconfig: {}".format(self.db)) + logger.debug("Set Databaseconfig: {}".format(self.db)) if 'LDAP' not in self.config: self.__error__( @@ -39,79 +40,79 @@ class ConifgParser(): self.__error__( 'Wrong Configuration for LDAP. You should configure ldapconfig with "URL" and "BIND_DN"') if 'PORT' not in self.config['LDAP']: - DEBUG.info( + 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']: - DEBUG.info( + 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']: - DEBUG.info( + 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']: - DEBUG.info( + 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']: - DEBUG.info( + 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']: - DEBUG.info( + 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']: - DEBUG.info( + 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'] - DEBUG.info("Set LDAPconfig: {}".format(self.ldap)) + logger.debug("Set LDAPconfig: {}".format(self.ldap)) if 'AccessTokenLifeTime' in self.config: self.accessTokenLifeTime = int(self.config['AccessTokenLifeTime']) - DEBUG.info("Set AccessTokenLifeTime: {}".format( + logger.info("Set AccessTokenLifeTime: {}".format( self.accessTokenLifeTime)) else: self.accessTokenLifeTime = default['AccessTokenLifeTime'] - DEBUG.info("No Config for AccessTokenLifetime found. Set it to default: {}".format( + 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'] - DEBUG.info('No Conifg for Mail found. Set it to defaul: {}'.format( + 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'] - DEBUG.info("No Config for URL in Mail found. Set it to default") + 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'] - DEBUG.info("No Config for port in Mail found. Set it to default") + logger.info("No Config for port in Mail found. Set it to default") else: self.config['Mail']['port'] = int(self.config['Mail']['port']) - DEBUG.info("No Conifg for port in Mail found. Set it to default") + 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'] - DEBUG.info("No Config for user in Mail found. Set it to default") + 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'] - DEBUG.info("No Config for passwd in Mail found. Set it to default") + 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'] - DEBUG.info("No Config for email in Mail found. Set it to default") + 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'] - DEBUG.info("No Config for crypt in Mail found. Set it to default") + logger.info("No Config for crypt in Mail found. Set it to default") self.mail = self.config['Mail'] - DEBUG.info('Set Mailconfig: {}'.format(self.mail)) + logger.debug('Set Mailconfig: {}'.format(self.mail)) def getLDAP(self): return self.ldap @@ -126,7 +127,7 @@ class ConifgParser(): return self.mail def __error__(self, msg): - DEBUG.error(msg, exc_info=True) + logger.error(msg, exc_info=True) sys.exit(-1) diff --git a/flaschengeist/system/database.py b/flaschengeist/system/database.py new file mode 100644 index 0000000..f0b13d6 --- /dev/null +++ b/flaschengeist/system/database.py @@ -0,0 +1,3 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() diff --git a/flaschengeist/system/decorator.py b/flaschengeist/system/decorator.py index fe9fb58..d948a50 100644 --- a/flaschengeist/system/decorator.py +++ b/flaschengeist/system/decorator.py @@ -1,6 +1,7 @@ from functools import wraps -from .logger import getDebugLogger -DEBUG = getDebugLogger() +from flask import current_app +from werkzeug.local import LocalProxy +logger = LocalProxy(lambda: current_app.logger) def login_required(**kwargs): @@ -14,24 +15,24 @@ def login_required(**kwargs): groups = kwargs["groups"] if "bar" in kwargs: bar = kwargs["bar"] - DEBUG.debug("groups are {{ {} }}".format(groups)) + logger.debug("groups are {{ {} }}".format(groups)) def real_decorator(func): @wraps(func) def wrapper(*args, **kwargs): token = request.headers.get('Token') - DEBUG.debug("token is {{ {} }}".format(token)) + logger.debug("token is {{ {} }}".format(token)) accToken = accessController.validateAccessToken(token, groups) - DEBUG.debug("accToken is {{ {} }}".format(accToken)) + logger.debug("accToken is {{ {} }}".format(accToken)) kwargs['accToken'] = accToken if accToken: - DEBUG.debug("token {{ {} }} is valid".format(token)) + logger.debug("token {{ {} }} is valid".format(token)) if accToken.lock_bar and not bar: return jsonify({"error": "error", "message": "permission forbidden"}), 403 return func(*args, **kwargs) else: - DEBUG.warning("token {{ {} }} is not valid".format(token)) + logger.warning("token {{ {} }} is not valid".format(token)) return jsonify({"error": "error", "message": "permission denied"}), 401 return wrapper diff --git a/flaschengeist/system/logger.py b/flaschengeist/system/logger.py deleted file mode 100644 index 67c626f..0000000 --- a/flaschengeist/system/logger.py +++ /dev/null @@ -1,32 +0,0 @@ -import logging -import logging.config -import yaml -from os import path, makedirs, getcwd -from .. import _modpath - -fname = _modpath/'logging.yml' - -if not path.exists("geruecht/log/debug"): - a = path.join(path.curdir, "geruecht", "log", "debug") - makedirs(a) - -if not path.exists("geruecht/log/info"): - b = path.join(path.curdir, "geruecht", "log", "info") - makedirs(b) - - -with fname.open(mode='rb') as file: - config = yaml.safe_load(file.read()) -logging.config.dictConfig(config) - - -def getDebugLogger(): - return logging.getLogger("debug_logger") - - -def getCreditLogger(): - return logging.getLogger("credit_logger") - - -def getJobsLogger(): - return logging.getLogger("jobs_logger") diff --git a/flaschengeist/system/model/user.py b/flaschengeist/system/model/user.py deleted file mode 100644 index 157a965..0000000 --- a/flaschengeist/system/model/user.py +++ /dev/null @@ -1,49 +0,0 @@ -from ..logger import getDebugLogger -from datetime import datetime -debug = getDebugLogger() - - -class User(): - """ Database Object for User - - Table for all safed User - - Attributes: - id: Id in Database as Primary Key. - username: Username of the User to Login - firstname: Firstname of the User - lastname: Lastname of the User - mail: mail address of the User - """ - def __init__(self, data): - debug.info("init user") - if 'id' in data: - self.id = int(data['id']) - self.firstname = data['firstname'] - self.lastname = data['lastname'] - if 'mail' in data: - self.mail = data['mail'] - else: - self.mail = '' - if 'username' in data: - self.username = data['username'] - else: - self.username = None - debug.debug("user is {{ {} }}".format(self)) - - def updateData(self, data): - debug.info("update data of user") - if 'firstname' in data: - self.firstname = data['firstname'] - if 'lastname' in data: - self.lastname = data['lastname'] - if 'mail' in data: - self.mail = data['mail'] - if 'username' in data: - self.username = data['username'] - - def __repr__(self): - return "User({}, {})".format(self.uid, self.username) - - -# TODO: user attributes (uid|key|value), getter, setter, has diff --git a/flaschengeist/system/model/__init__.py b/flaschengeist/system/models/__init__.py similarity index 100% rename from flaschengeist/system/model/__init__.py rename to flaschengeist/system/models/__init__.py diff --git a/flaschengeist/system/model/accessToken.py b/flaschengeist/system/models/accessToken.py similarity index 60% rename from flaschengeist/system/model/accessToken.py rename to flaschengeist/system/models/accessToken.py index 6072378..fc19e96 100644 --- a/flaschengeist/system/model/accessToken.py +++ b/flaschengeist/system/models/accessToken.py @@ -1,9 +1,10 @@ from datetime import datetime -from ..logger import getDebugLogger +from ..database import db +from flask import current_app +from werkzeug.local import LocalProxy +logger = LocalProxy(lambda: current_app.logger) -debug = getDebugLogger() - -class AccessToken(): +class AccessToken(db.Model): """ Model for an AccessToken Attributes: @@ -11,39 +12,23 @@ class AccessToken(): user: Is an User. token: String to verify access later. """ - - timestamp = None - user = None - token = None - - def __init__(self, id, user, token, lifetime, lock_bar=False, timestamp=datetime.now(), browser=None, platform=None): - """ Initialize Class AccessToken - - No more to say. - - Args: - User: Is an User to set. - token: Is a String to verify later - timestamp: Default current time, but can set to an other datetime-Object. - """ - debug.debug("init accesstoken") - self.id = id - self.user = user - self.timestamp = timestamp - self.lifetime = lifetime - self.token = token - self.lock_bar = lock_bar - self.browser = browser - self.platform = platform - debug.debug("accesstoken is {{ {} }}".format(self)) + __tablename__ = 'session' + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, default=datetime.utcnow) + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + user = db.relationship("User", back_populates="sessions") + token = db.Column(db.String(30)) + lifetime = db.Column(db.Integer) + browser = db.Column(db.String(30)) + platform = db.Column(db.String(30)) def updateTimestamp(self): """ Update the Timestamp Update the Timestamp to the current Time. """ - debug.debug("update timestamp from accesstoken {{ {} }}".format(self)) - self.timestamp = datetime.now() + logger.debug("update timestamp from accesstoken {{ {} }}".format(self)) + self.timestamp = datetime.utcnow() def toJSON(self): """ Create Dic to dump in JSON diff --git a/flaschengeist/system/models/user.py b/flaschengeist/system/models/user.py new file mode 100644 index 0000000..8677f5b --- /dev/null +++ b/flaschengeist/system/models/user.py @@ -0,0 +1,63 @@ +from datetime import datetime +from ..database import db +from .accessToken import AccessToken +from sqlalchemy.orm.collections import attribute_mapped_collection +from flask import current_app +from werkzeug.local import LocalProxy +logger = LocalProxy(lambda: current_app.logger) + +association_table = db.Table('user_group', + db.Column('user_id', db.Integer, db.ForeignKey('user.id')), + db.Column('group_id', db.Integer, db.ForeignKey('group.id')) +) + +class User(db.Model): + """ Database Object for User + + Table for all safed User + + Attributes: + id: Id in Database as Primary Key. + uid: User ID used by authentification provider + displayname: Name to show + firstname: Firstname of the User + lastname: Lastname of the User + mail: mail address of the User + """ + __tablename__ = 'user' + id = db.Column(db.Integer, primary_key=True) + uid = db.Column(db.String(30)) + displayname = db.Column(db.String(20)) + firstname = db.Column(db.String(20)) + lastname = db.Column(db.String(20)) + mail = db.Column(db.String(20)) + groups = db.relationship("UserGroup", secondary=association_table) + sessions = db.relationship("AccessToken", back_populates="user") + attributes = db.relationship("UserAttribute", collection_class=attribute_mapped_collection('name'), cascade="all, delete") + + def updateData(self, data): + logger.debug("update data of user") + if 'uid' in data: + self.uid = data['uid'] + if 'firstname' in data: + self.firstname = data['firstname'] + if 'lastname' in data: + self.lastname = data['lastname'] + if 'mail' in data: + self.mail = data['mail'] + if 'displayname' in data: + self.displayname = data['displayname'] + + +class UserAttribute(db.Model): + __tablename__ = 'userAttribute' + id = db.Column(db.Integer, primary_key=True) + user = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + name = db.Column(db.String(30)) + value = db.Column(db.String(192)) + +class UserGroup(db.Model): + __tablename__ = 'group' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(30)) + diff --git a/run_flaschengeist b/run_flaschengeist index ab4b3b2..41f9494 100644 --- a/run_flaschengeist +++ b/run_flaschengeist @@ -1,5 +1,5 @@ #!/usr/bin/python3 -from flaschengeist.app import app +from flaschengeist import create_app """ Main @@ -7,4 +7,4 @@ from flaschengeist.app import app """ if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0') + create_app().run(debug=True, host='0.0.0.0') diff --git a/setup.py b/setup.py index 09cdb58..50a8de7 100644 --- a/setup.py +++ b/setup.py @@ -10,10 +10,13 @@ setup( packages=find_packages(), package_data={'': ['*.yml']}, scripts=['run_flaschengeist'], - install_requires=['Flask >= 1.1', 'PyYAML>=5.3.1', "flask_mysqldb", "flask_ldapconn", "flask_cors"], + install_requires=['Flask >= 1.1', 'PyYAML>=5.3.1', 'sqlalchemy>=1.3', "flask_sqlalchemy", "flask_ldapconn", "flask_cors"], entry_points = { - 'flaschengeist.plugins': [ - 'users = flaschengeist.modules.user:register' + 'flaschengeist.plugin': [ + 'user = flaschengeist.modules.user:register' + ], + 'flaschengeist.auth': [ + 'plain_auth = flaschengeist.modules.auth_plain:AuthPlain' ] } )