From a000ccfb1cf6c590af9970bd84a77eb5cb278a7e Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sat, 22 Aug 2020 16:47:56 +0200 Subject: [PATCH] Added modules for authentification. * Added base class for auth plugins * Provide plain_auth (using password authentification) * Provide module for login and logout handling --- flaschengeist/__init__.py | 5 + flaschengeist/logging.yml | 12 +- flaschengeist/modules/__init__.py | 29 +++ flaschengeist/modules/auth/__init__.py | 77 ++++++ flaschengeist/modules/auth_plain/__init__.py | 24 ++ flaschengeist/modules/user/__init__.py | 56 ---- flaschengeist/modules/user/routes.py | 216 ---------------- flaschengeist/modules/user/user.py | 244 ------------------ flaschengeist/system/controller/__init__.py | 4 - .../system/controller/accesTokenController.py | 87 +++---- .../controller/mainController/__init__.py | 83 +++--- .../mainController/mainUserController.py | 37 ++- flaschengeist/system/decorator.py | 32 +-- flaschengeist/system/models/accessToken.py | 2 +- flaschengeist/system/models/user.py | 10 + setup.py | 2 +- 16 files changed, 256 insertions(+), 664 deletions(-) create mode 100644 flaschengeist/modules/auth/__init__.py create mode 100644 flaschengeist/modules/auth_plain/__init__.py delete mode 100644 flaschengeist/modules/user/__init__.py delete mode 100644 flaschengeist/modules/user/routes.py delete mode 100644 flaschengeist/modules/user/user.py diff --git a/flaschengeist/__init__.py b/flaschengeist/__init__.py index 1a29c59..9d97fd0 100644 --- a/flaschengeist/__init__.py +++ b/flaschengeist/__init__.py @@ -19,6 +19,11 @@ with (_modpath/'logging.yml').open(mode='rb') as file: config = yaml.safe_load(file.read()) dictConfig(config) +import logging +from werkzeug.local import LocalProxy +logger = LocalProxy(lambda: logging.getLogger(__name__)) + + def create_app(): app = Flask(__name__) CORS(app) diff --git a/flaschengeist/logging.yml b/flaschengeist/logging.yml index 6ca9241..715b92d 100644 --- a/flaschengeist/logging.yml +++ b/flaschengeist/logging.yml @@ -37,11 +37,6 @@ handlers: encoding: utf8 loggers: - debug_logger: - level: DEBUG - handlers: [console, debug] - propagate: no - credit_logger: level: INFO handlers: [credit] @@ -52,6 +47,9 @@ loggers: handlers: [jobs] propagate: no + werkzeug: + level: WARNING + root: - level: INFO - handlers: [console, debug] \ No newline at end of file + level: DEBUG + handlers: [console, debug] diff --git a/flaschengeist/modules/__init__.py b/flaschengeist/modules/__init__.py index e69de29..a0c81bc 100644 --- a/flaschengeist/modules/__init__.py +++ b/flaschengeist/modules/__init__.py @@ -0,0 +1,29 @@ +class Auth(): + def login(self, user, pw): + """ + user User class containing at least the uid + pw given password + + HAS TO BE IMPLEMENTED! + + should return False if not found or invalid credentials + should return True if success + """ + return False + + def updateUser(self, user): + """ + user User class + + If backend is using external data, then update this user instance with external data + """ + pass + + def modifyUser(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 new file mode 100644 index 0000000..1559465 --- /dev/null +++ b/flaschengeist/modules/auth/__init__.py @@ -0,0 +1,77 @@ +############################################# +# Plugin: Auth # +# Functionality: Allow management of # +# authentification, login, logout, etc # +############################################# + +from flask import Blueprint, current_app, request, jsonify +from werkzeug.local import LocalProxy + +from flaschengeist.system.decorator import login_required +from flaschengeist.system.exceptions import PermissionDenied +from flaschengeist.system.controller import mainController as mc +import flaschengeist.system.controller.accesTokenController as ac + +logger = LocalProxy(lambda: current_app.logger) +accesTokenController = LocalProxy(lambda: ac.AccesTokenController()) + +auth_bp = Blueprint('auth', __name__) + +def register(): + return auth_bp + +############################################ +## Routes ## +############################################ + +@auth_bp.route("/logout", methods=['GET']) +@login_required() +def _logout(**kwargs): + try: + logger.debug("logout user") + accToken = kwargs['accToken'] + logger.debug("accesstoken is {{ {} }}".format(accToken)) + logger.debug("delete accesstoken") + accesTokenController.deleteAccessToken(accToken) + logger.info("return ok logout user") + return jsonify({"ok": "ok"}) + except Exception as err: + logger.warning("exception in logout user.", exc_info=True) + return jsonify({"error": str(err)}), 500 + + +@auth_bp.route("/login", methods=['POST']) +def _login(): + """ Login User + + Nothing to say. + Login in User and create an AccessToken for the User. + + Returns: + A JSON-File with createt Token or Errors + """ + logger.debug("Start log in.") + data = request.get_json() + logger.info(request) + username = data['username'] + password = data['password'] + logger.debug("username is {{ {} }}".format(username)) + try: + logger.debug("search {{ {} }} in database".format(username)) + mainController = mc.MainController() + user = mainController.loginUser(username, password) + logger.debug("user is {{ {} }}".format(user)) + token = accesTokenController.createAccesToken(user, user_agent=request.user_agent) + logger.debug("accesstoken is {{ {} }}".format(token)) + logger.debug("validate accesstoken") + dic = user.toJSON() + dic["token"] = token + logger.info("User {{ {} }} success login.".format(username)) + logger.debug("return login {{ {} }}".format(dic)) + return jsonify(dic) + except PermissionDenied as err: + logger.debug("permission denied exception in login", exc_info=True) + return jsonify({"error": str(err)}), 401 + except Exception as err: + logger.error("exception in login.", exc_info=True) + return jsonify({"error": "permission denied"}), 401 diff --git a/flaschengeist/modules/auth_plain/__init__.py b/flaschengeist/modules/auth_plain/__init__.py new file mode 100644 index 0000000..037533c --- /dev/null +++ b/flaschengeist/modules/auth_plain/__init__.py @@ -0,0 +1,24 @@ +import hashlib, binascii, os +import flaschengeist.modules as modules + +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 False + + def __hash_password(self, password): + salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') + pwdhash = hashlib.pbkdf2_hmac('sha3-512', password.encode('utf-8'), salt, 100000) + pwdhash = binascii.hexlify(pwdhash) + return (salt + pwdhash).decode('ascii') + + def __verify_password(self, stored_password, provided_password): + salt = stored_password[:64] + stored_password = stored_password[64:] + pwdhash = hashlib.pbkdf2_hmac('sha3-512', provided_password.encode('utf-8'), + salt.encode('ascii'), 100000) + pwdhash = binascii.hexlify(pwdhash).decode('ascii') + return pwdhash == stored_password diff --git a/flaschengeist/modules/user/__init__.py b/flaschengeist/modules/user/__init__.py deleted file mode 100644 index ec9f6f3..0000000 --- a/flaschengeist/modules/user/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -####################################### -# Plugin: Users # -# Functionality: Allow management # -# of users, login, logout, etc # -####################################### - -from flask import Blueprint -from flaschengeist.app import app - -def register(): - return Blueprint('user', __name__) - -####################################### -## Routes ## -####################################### -#dummy -@app.route("/") -def _dummy(): - return 'Noch funktioniert hier mal überhaupt nichts!' - -@app.route("/login", methods=['POST']) -def _login(): - """ Login User - - Nothing to say. - Login in User and create an AccessToken for the User. - - Returns: - A JSON-File with createt Token or Errors - """ - debug.info("Start log in.") - data = request.get_json() - username = data['username'] - password = data['password'] - debug.debug("username is {{ {} }}".format(username)) - try: - user_agent = request.user_agent - debug.info("search {{ {} }} in database".format(username)) - user = mainController.loginUser(username, password) - debug.debug("user is {{ {} }}".format(user)) - token = accesTokenController.createAccesToken(user, user_agent=user_agent) - debug.debug("accesstoken is {{ {} }}".format(token)) - debug.info("validate accesstoken") - dic = accesTokenController.validateAccessToken( - token, [USER, EXTERN]).user.toJSON() - dic["token"] = token - dic["accessToken"] = token - debug.info("User {{ {} }} success login.".format(username)) - debug.info("return login {{ {} }}".format(dic)) - return jsonify(dic) - except PermissionDenied as err: - debug.warning("permission denied exception in logout", exc_info=True) - return jsonify({"error": str(err)}), 401 - except Exception as err: - debug.warning("exception in logout.", exc_info=True) - return jsonify({"error": "permission denied"}), 401 diff --git a/flaschengeist/modules/user/routes.py b/flaschengeist/modules/user/routes.py deleted file mode 100644 index 07b78ce..0000000 --- a/flaschengeist/modules/user/routes.py +++ /dev/null @@ -1,216 +0,0 @@ -from geruecht import app -from geruecht.logger import getDebugLogger -from geruecht.decorator import login_required -from geruecht.exceptions import PermissionDenied -import geruecht.controller.accesTokenController as ac -import geruecht.controller.mainController as mc -from geruecht.model import MONEY, BAR, USER, GASTRO, VORSTAND, EXTERN -from flask import request, jsonify - -accesTokenController = ac.AccesTokenController() -mainController = mc.MainController() - -debug = getDebugLogger() - -@app.route("/valid", methods=['POST']) -@login_required(bar=True) -def _valid(**kwargs): - debug.info('/valid') - try: - accToken = kwargs['accToken'] - data = request.get_json() - mainController.validateUser(accToken.user.uid, data['password']) - debug.debug('return {{ "ok": "ok" }}') - return jsonify({"ok": "ok"}) - except Exception as err: - debug.warning("exception in valide.", exc_info=True) - return jsonify({"error": str(err)}), 500 - -@app.route("/pricelist", methods=['GET']) -def _getPricelist(): - try: - debug.info("get pricelist") - retVal = mainController.getPricelist() - debug.info("return pricelist {{ {} }}".format(retVal)) - return jsonify(retVal) - except Exception as err: - debug.warning("exception in get pricelist.", exc_info=True) - return jsonify({"error": str(err)}), 500 - - -@app.route('/drinkTypes', methods=['GET']) -def getTypes(): - try: - debug.info("get drinktypes") - retVal = mainController.getAllDrinkTypes() - debug.info("return drinktypes {{ {} }}".format(retVal)) - return jsonify(retVal) - except Exception as err: - debug.warning("exception in get drinktypes.", exc_info=True) - return jsonify({"error": str(err)}), 500 - - -@app.route('/getAllStatus', methods=['GET']) -@login_required(groups=[USER, MONEY, GASTRO, BAR, VORSTAND], bar=True) -def _getAllStatus(**kwargs): - try: - debug.info("get all status for users") - retVal = mainController.getAllStatus() - debug.info("return all status for users {{ {} }}".format(retVal)) - return jsonify(retVal) - except Exception as err: - debug.warning("exception in get all status for users.", exc_info=True) - return jsonify({"error": str(err)}), 500 - - -@app.route('/getStatus', methods=['POST']) -@login_required(groups=[USER, MONEY, GASTRO, BAR, VORSTAND], bar=True) -def _getStatus(**kwargs): - try: - debug.info("get status from user") - data = request.get_json() - name = data['name'] - debug.info("get status from user {{ {} }}".format(name)) - retVal = mainController.getStatus(name) - debug.info( - "return status from user {{ {} }} : {{ {} }}".format(name, retVal)) - return jsonify(retVal) - except Exception as err: - debug.warning("exception in get status from user.", exc_info=True) - return jsonify({"error": str(err)}), 500 - - -@app.route('/getUsers', methods=['GET']) -@login_required(groups=[USER], bar=True) -def _getUsers(**kwargs): - try: - extern = True - if 'extern' in request.args: - extern = not bool(int(request.args['extern'])) - debug.info("get all users from database") - users = mainController.getAllUsersfromDB(extern=extern) - debug.debug("users are {{ {} }}".format(users)) - retVal = [user.toJSON() for user in users] - debug.info("return all users from database {{ {} }}".format(retVal)) - return jsonify(retVal) - except Exception as err: - debug.warning( - "exception in get all users from database.", exc_info=True) - return jsonify({"error": str(err)}), 500 - - -@app.route("/getLifeTime", methods=['GET']) -@login_required(groups=[MONEY, GASTRO, VORSTAND, EXTERN, USER], bar=True) -def _getLifeTime(**kwargs): - try: - debug.info("get lifetime of accesstoken") - if 'accToken' in kwargs: - accToken = kwargs['accToken'] - debug.debug("accessToken is {{ {} }}".format(accToken)) - retVal = {"value": accToken.lifetime, - "group": accToken.user.toJSON()['group'], - "lock_bar": accToken.lock_bar} - debug.info( - "return get lifetime from accesstoken {{ {} }}".format(retVal)) - return jsonify(retVal) - except Exception as err: - debug.info("exception in get lifetime of accesstoken.", exc_info=True) - return jsonify({"error": str(err)}), 500 - - -@app.route("/saveLifeTime", methods=['POST']) -@login_required(groups=[MONEY, GASTRO, VORSTAND, EXTERN, USER], bar=True) -def _saveLifeTime(**kwargs): - try: - debug.info("save lifetime for accessToken") - if 'accToken' in kwargs: - accToken = kwargs['accToken'] - debug.debug("accessToken is {{ {} }}".format(accToken)) - data = request.get_json() - lifetime = data['value'] - debug.debug("lifetime is {{ {} }}".format(lifetime)) - debug.info("set lifetime {{ {} }} to accesstoken {{ {} }}".format( - lifetime, accToken)) - accToken.lifetime = lifetime - debug.info("update accesstoken timestamp") - accToken = accesTokenController.updateAccessToken(accToken) - accToken = accesTokenController.validateAccessToken(accToken.token, [USER, EXTERN]) - retVal = {"value": accToken.lifetime, - "group": accToken.user.toJSON()['group']} - debug.info( - "return save lifetime for accessToken {{ {} }}".format(retVal)) - return jsonify(retVal) - except Exception as err: - debug.warning( - "exception in save lifetime for accesstoken.", exc_info=True) - return jsonify({"error": str(err)}), 500 - -@app.route("/passwordReset", methods=['POST']) -def _passwordReset(): - try: - debug.info('password reset') - data = request.get_json() - mail = mainController.resetPassword(data) - index = mail.find('@') - for i in range(index): - if i == 0: - continue - mail = mail.replace(mail[i], "*", 1) - return jsonify({"ok": "ok", "mail": mail}) - except Exception as err: - debug.warning("excetpion in password reset", exc_info=True) - return jsonify({"error": str(err)}), 409 - -@app.route("/logout", methods=['GET']) -@login_required(groups=[MONEY, GASTRO, VORSTAND, EXTERN, USER], bar=True) -def _logout(**kwargs): - try: - debug.info("logout user") - if 'accToken' in kwargs: - accToken = kwargs['accToken'] - debug.debug("accesstoken is {{ {} }}".format(accToken)) - debug.info("delete accesstoken") - accesTokenController.deleteAccessToken(accToken) - debug.info("return ok logout user") - return jsonify({"ok": "ok"}) - except Exception as err: - debug.warning("exception in logout user.", exc_info=True) - return jsonify({"error": str(err)}), 500 - - -@app.route("/login", methods=['POST']) -def _login(): - """ Login User - - Nothing to say. - Login in User and create an AccessToken for the User. - - Returns: - A JSON-File with createt Token or Errors - """ - debug.info("Start log in.") - data = request.get_json() - username = data['username'] - password = data['password'] - debug.debug("username is {{ {} }}".format(username)) - try: - user_agent = request.user_agent - debug.info("search {{ {} }} in database".format(username)) - user = mainController.loginUser(username, password) - debug.debug("user is {{ {} }}".format(user)) - token = accesTokenController.createAccesToken(user, user_agent=user_agent) - debug.debug("accesstoken is {{ {} }}".format(token)) - debug.info("validate accesstoken") - dic = accesTokenController.validateAccessToken( - token, [USER, EXTERN]).user.toJSON() - dic["token"] = token - dic["accessToken"] = token - debug.info("User {{ {} }} success login.".format(username)) - debug.info("return login {{ {} }}".format(dic)) - return jsonify(dic) - except PermissionDenied as err: - debug.warning("permission denied exception in logout", exc_info=True) - return jsonify({"error": str(err)}), 401 - except Exception as err: - debug.warning("exception in logout.", exc_info=True) - return jsonify({"error": "permission denied"}), 401 diff --git a/flaschengeist/modules/user/user.py b/flaschengeist/modules/user/user.py deleted file mode 100644 index 543859c..0000000 --- a/flaschengeist/modules/user/user.py +++ /dev/null @@ -1,244 +0,0 @@ -from geruecht.logger import getDebugLogger -from geruecht.model.creditList import CreditList, create_empty_data -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. - userID: ID for the User maybe to Link? - username: Username of the User to Login - firstname: Firstname of the User - Lastname: Lastname of the User - group: Which group is the User? moneymaster, gastro, user or bar? - password: salted hashed password for the User. - """ - def __init__(self, data): - debug.info("init user") - if 'id' in data: - self.id = int(data['id']) - self.uid = data['uid'] - self.dn = data['dn'] - self.firstname = data['firstname'] - self.lastname = data['lastname'] - self.group = data['gruppe'] - self.last_seen = None - if 'last_seen' in data: - self.last_seen = data['last_seen'] - if 'statusgroup' in data: - self.statusgroup = data['statusgroup'] - else: - self.statusgroup = None - if 'voting' in data: - self.voting = data['voting'] - else: - self.voting = None - if 'mail' in data: - self.mail = data['mail'] - else: - self.mail = '' - if 'lockLimit' in data: - self.limit = int(data['lockLimit']) - else: - self.limit = 4200 - if 'locked' in data: - self.locked = bool(data['locked']) - else: - self.locked = False - if 'autoLock' in data: - self.autoLock = bool(data['autoLock']) - else: - self.autoLock = True - if type(data['gruppe']) == list: - self.group = data['gruppe'] - elif type(data['gruppe']) == str: - self.group = data['gruppe'].split(',') - if 'creditLists' in data: - self.geruechte = data['creditLists'] - if 'workgroups' in data: - self.workgroups = data['workgroups'] - else: - self.workgroups = None - self.password = '' - debug.debug("user is {{ {} }}".format(self)) - - def updateData(self, data): - debug.info("update data of user") - if 'dn' in data: - self.dn = data['dn'] - if 'firstname' in data: - self.firstname = data['firstname'] - if 'lastname' in data: - self.lastname = data['lastname'] - if 'gruppe' in data: - self.group = data['gruppe'] - if 'lockLimit' in data: - self.limit = int(data['lockLimit']) - if 'locked' in data: - self.locked = bool(data['locked']) - if 'autoLock' in data: - self.autoLock = bool(data['autoLock']) - if 'mail' in data: - self.mail = data['mail'] - if 'statusgorup' in data: - self.statusgroup = data['statusgroup'] - if 'voting' in data: - self.voting = data['voting'] - if 'workgroups' in data: - self.workgroups = data['workgroups'] - else: - self.workgroups = None - - def initGeruechte(self, creditLists): - if type(creditLists) == list: - self.geruechte = creditLists - - def createGeruecht(self, amount=0, year=datetime.now().year): - """ Create Geruecht - - This function create a geruecht for the user for an year. - By default is amount zero and year the actual year. - - Args: - amount: is the last_schulden of the geruecht - year: is the year of the geruecht - - Returns: - the created geruecht - """ - debug.info("create creditlist for user {{ {} }} in year {{ {} }}".format(self, year)) - data = create_empty_data() - data['user_id'] = self.id - data['last_schulden'] = amount - data['year_date'] = year - credit = CreditList(data) - self.geruechte.append(credit) - debug.debug("creditlist is {{ {} }}".format(credit)) - return credit - - def getGeruecht(self, year=datetime.now().year): - """ Get Geruecht - - This function returns the geruecht of an year. - By default is the year the actual year. - - Args: - year: the year of the geruecht - - Returns: - the geruecht of the year - """ - debug.info("get creditlist from user on year {{ {} }}".format(year)) - for geruecht in self.geruechte: - if geruecht.year == year: - debug.debug("creditlist is {{ {} }} for user {{ {} }}".format(geruecht, self)) - return geruecht - debug.debug("no creditlist found for user {{ {} }}".format(self)) - geruecht = self.createGeruecht(year=year) - - return self.getGeruecht(year=year) - - def addAmount(self, amount, year=datetime.now().year, month=datetime.now().month): - """ Add Amount - - This function add an amount to a geruecht with an spezified year and month to the user. - By default the year is the actual year. - By default the month is the actual month. - - Args: - year: year of the geruecht - month: month for the amount - - Returns: - double (credit, amount) - """ - debug.info("add amount to user {{ {} }} in year {{ {} }} and month {{ {} }}".format(self, year, month)) - geruecht = self.getGeruecht(year=year) - retVal = geruecht.addAmount(amount, month=month) - - return retVal - - def addCredit(self, credit, year=datetime.now().year, month=datetime.now().month): - """ Add Credit - - This function add an credit to a geruecht with an spezified year and month to the user. - By default the year is the actual year. - By default the month is the actual month. - - Args: - year: year of the geruecht - month: month for the amount - - Returns: - double (credit, amount) - """ - debug.info("add credit to user {{ {} }} in year {{ {} }} and month {{ {} }}".format(self, year, month)) - geruecht = self.getGeruecht(year=year) - retVal = geruecht.addCredit(credit, month=month) - - return retVal - - def updateGeruecht(self): - """ Update list of geruechte - - This function iterate through the geruechte, which sorted by year and update the last_schulden of the geruecht. - """ - debug.info("update all creditlists ") - self.geruechte.sort(key=self.sortYear) - - for index, geruecht in enumerate(self.geruechte): - if index == 0 or index == len(self.geruechte) - 1: - geruecht.last_schulden = 0 - if index != 0: - geruecht.last_schulden = (self.geruechte[index - 1].getSchulden() * -1) - - return self.geruechte - - def sortYear(self, geruecht): - """ Sort Year - - This function is only an helperfunction to sort the list of geruechte by years. - It only returns the year of the geruecht. - - Args: - geruecht: geruecht which year you want - - Returns: - int year of the geruecht - """ - return geruecht.year - - def toJSON(self): - """ Create Dic to dump in JSON - - Returns: - A Dic with static Attributes. - """ - dic = { - "id": self.id, - "userId": self.uid, - "uid": self.uid, - "dn": self.dn, - "firstname": self.firstname, - "lastname": self.lastname, - "group": self.group, - "username": self.uid, - "locked": self.locked, - "autoLock": self.autoLock, - "limit": self.limit, - "mail": self.mail, - "statusgroup": self.statusgroup, - "voting": self.voting, - "workgroups": self.workgroups - } - return dic - - def __repr__(self): - return "User({}, {}, {})".format(self.uid, self.dn, self.group) - diff --git a/flaschengeist/system/controller/__init__.py b/flaschengeist/system/controller/__init__.py index 7b2f4c6..095c03c 100644 --- a/flaschengeist/system/controller/__init__.py +++ b/flaschengeist/system/controller/__init__.py @@ -1,12 +1,8 @@ -from ..logger import getDebugLogger from ..configparser import ConifgParser from flaschengeist import _modpath -import os config = ConifgParser(_modpath/'config.yml') -LOGGER = getDebugLogger() - class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): diff --git a/flaschengeist/system/controller/accesTokenController.py b/flaschengeist/system/controller/accesTokenController.py index bec7703..c6274be 100644 --- a/flaschengeist/system/controller/accesTokenController.py +++ b/flaschengeist/system/controller/accesTokenController.py @@ -1,17 +1,13 @@ -from geruecht.model.accessToken import AccessToken -import geruecht.controller as gc -import geruecht.controller.mainController as mc -import geruecht.controller.databaseController as dc -from geruecht.model import BAR +from ..models.accessToken import AccessToken +from flaschengeist.system.database import db + from datetime import datetime, timedelta -import hashlib +import secrets from . import Singleton -from geruecht.logger import getDebugLogger +from flask import Blueprint, request, jsonify +import logging -debug = getDebugLogger() - -mainController = mc.MainController() -db = dc.DatabaseController() +logger = logging.getLogger("flaschenpost") class AccesTokenController(metaclass=Singleton): """ Control all createt AccesToken @@ -30,22 +26,8 @@ class AccesTokenController(metaclass=Singleton): Initialize Thread and set tokenList empty. """ - debug.info("init accesstoken controller") - self.lifetime = gc.accConfig - - def checkBar(self, user): - debug.info("check if user {{ {} }} is baruser".format(user)) - if (mainController.checkBarUser(user)): - if BAR not in user.group: - debug.debug("append bar to user {{ {} }}".format(user)) - user.group.append(BAR) - return True - else: - while BAR in user.group: - debug.debug("delete bar from user {{ {} }}".format(user)) - user.group.remove(BAR) - return False - debug.debug("user {{ {} }} groups are {{ {} }}".format(user, user.group)) + logger.debug("init accesstoken controller") + self.lifetime = lifetime def validateAccessToken(self, token, group): """ Verify Accestoken @@ -59,27 +41,27 @@ class AccesTokenController(metaclass=Singleton): Returns: An the AccesToken for this given Token or False. """ - debug.info("check token {{ {} }} is valid") - for accToken in db.getAccessTokens(): - debug.debug("accesstoken is {}".format(accToken)) + logger.debug("check token {{ {} }} is valid".format(token)) + for accToken in AccessToken.query.filter_by(token=token): endTime = accToken.timestamp + timedelta(seconds=accToken.lifetime) - now = datetime.now() - debug.debug("now is {{ {} }}, endtime is {{ {} }}".format(now, endTime)) + now = datetime.utcnow() + logger.debug("now is {{ {} }}, endtime is {{ {} }}".format(now, endTime)) if now <= endTime: - debug.debug("check if token {{ {} }} is same as {{ {} }}".format(token, accToken)) + logger.debug("check if token {{ {} }} is same as {{ {} }}".format(token, accToken)) if accToken == token: - if not self.checkBar(accToken.user): - accToken.lock_bar = False - debug.debug("check if accestoken {{ {} }} has group {{ {} }}".format(accToken, group)) - if self.isSameGroup(accToken, group): - accToken.updateTimestamp() - db.updateAccessToken(accToken) - debug.debug("found accesstoken {{ {} }} with token: {{ {} }} and group: {{ {} }}".format(accToken, token, group)) - return accToken + # if not self.checkBar(accToken.user): + # accToken.lock_bar = False + # logger.debug("check if accestoken {{ {} }} has group {{ {} }}".format(accToken, group)) + # if self.isSameGroup(accToken, group): + accToken.updateTimestamp() + db.session.commit() + # logger.debug("found accesstoken {{ {} }} with token: {{ {} }} and group: {{ {} }}".format(accToken, token, group)) + return accToken else: - debug.debug("accesstoken is {{ {} }} out of date".format(accToken)) - db.deleteAccessToken(accToken) - debug.debug("no valid accesstoken with token: {{ {} }} and group: {{ {} }}".format(token, group)) + logger.debug("accesstoken is {{ {} }} out of date".format(accToken)) + db.session.delete(accToken) + db.session.commit() + logger.debug("no valid accesstoken with token: {{ {} }} and group: {{ {} }}".format(token, group)) return False def createAccesToken(self, user, user_agent=None): @@ -93,12 +75,13 @@ class AccesTokenController(metaclass=Singleton): Returns: A created Token for User """ - debug.info("creat accesstoken") - now = datetime.ctime(datetime.now()) - token = hashlib.md5((now + user.dn).encode('utf-8')).hexdigest() - self.checkBar(user) - accToken = db.createAccessToken(user, token, self.lifetime, datetime.now(), lock_bar=False, user_agent=user_agent) - debug.debug("accesstoken is {{ {} }}".format(accToken)) + logger.debug("creat accesstoken") + token = secrets.token_hex(16) + accToken = AccessToken(token=token, user=user, lifetime=self.lifetime, browser=user_agent.browser, platform=user_agent.platform) + db.session.add(accToken) + db.session.commit() + + logger.debug("accesstoken is {{ {} }}".format(accToken)) return token def isSameGroup(self, accToken, groups): @@ -122,7 +105,9 @@ class AccesTokenController(metaclass=Singleton): return db.getAccessTokensFromUser(user) def deleteAccessToken(self, accToken): - db.deleteAccessToken(accToken) + db.session.delete(accToken) + db.session.commit() + #AccessToken.query.filter_by(token=accToken).delete() def updateAccessToken(self, accToken): accToken.updateTimestamp() diff --git a/flaschengeist/system/controller/mainController/__init__.py b/flaschengeist/system/controller/mainController/__init__.py index ae3f73a..d426514 100644 --- a/flaschengeist/system/controller/mainController/__init__.py +++ b/flaschengeist/system/controller/mainController/__init__.py @@ -1,44 +1,37 @@ from .. import Singleton, mailConfig -import geruecht.controller.databaseController as dc -import geruecht.controller.ldapController as lc -import geruecht.controller.emailController as ec -from geruecht.model.user import User +from ...models.user import User from datetime import datetime, timedelta -from geruecht.logger import getDebugLogger -from ..mainController import mainJobKindController, mainCreditListController, mainPricelistController, mainUserController, mainWorkerController, mainWorkgroupController, mainJobInviteController, mainJobRequestController, mainRegistrationController, mainPasswordReset +from . import mainUserController +from ...database import db +from flask import current_app +from werkzeug.local import LocalProxy +logger = LocalProxy(lambda: current_app.logger) -db = dc.DatabaseController() -ldap = lc.LDAPController() -emailController = ec.EmailController() - -debug = getDebugLogger() - - -class MainController(mainJobKindController.Base, - mainCreditListController.Base, - mainPricelistController.Base, +class MainController(#mainJobKindController.Base, + #mainCreditListController.Base, + #mainPricelistController.Base, mainUserController.Base, - mainWorkerController.Base, - mainWorkgroupController.Base, - mainJobInviteController.Base, - mainJobRequestController.Base, - mainRegistrationController.Base, - mainPasswordReset.Base, + #mainWorkerController.Base, + #mainWorkgroupController.Base, + #mainJobInviteController.Base, + #mainJobRequestController.Base, + #mainRegistrationController.Base, + #mainPasswordReset.Base, metaclass=Singleton): def __init__(self): - debug.debug("init UserController") + logger.debug("init UserController") pass def setLockedDay(self, date, locked, hard=False): - debug.info( + logger.info( "set day locked on {{ {} }} with state {{ {} }}".format(date, locked)) retVal = db.setLockedDay(date.date(), locked, hard) - debug.debug("seted day locked is {{ {} }}".format(retVal)) + logger.debug("seted day locked is {{ {} }}".format(retVal)) return retVal def getLockedDays(self, from_date, to_date): - debug.info("get locked days from {{ {} }} to {{ {} }}".format( + logger.info("get locked days from {{ {} }} to {{ {} }}".format( from_date.date(), to_date.date())) oneDay = timedelta(1) delta = to_date.date() - from_date.date() @@ -48,11 +41,11 @@ class MainController(mainJobKindController.Base, startdate += oneDay lockday = self.getLockedDay(startdate) retVal.append(lockday) - debug.debug("lock days are {{ {} }}".format(retVal)) + logger.debug("lock days are {{ {} }}".format(retVal)) return retVal def getLockedDaysFromList(self, date_list): - debug.info("get locked days from list {{ {} }}".format(date_list)) + logger.info("get locked days from list {{ {} }}".format(date_list)) retVal = [] for on_date in date_list: day = datetime(on_date['on_date']['year'], on_date['on_date']['month'], on_date['on_date']['day'], 12) @@ -60,22 +53,22 @@ class MainController(mainJobKindController.Base, return retVal def getLockedDay(self, date): - debug.info("get locked day on {{ {} }}".format(date)) + logger.info("get locked day on {{ {} }}".format(date)) now = datetime.now() - debug.debug("now is {{ {} }}".format(now)) + logger.debug("now is {{ {} }}".format(now)) oldMonth = False - debug.debug("check if date old month or current month") + logger.debug("check if date old month or current month") for i in range(1, 8): if datetime(now.year, now.month, i).weekday() == 2: if now.day < i: oldMonth = True break - debug.debug("oldMonth is {{ {} }}".format(oldMonth)) + logger.debug("oldMonth is {{ {} }}".format(oldMonth)) lockedYear = now.year lockedMonth = now.month if now.month < now.month else now.month - \ 1 if oldMonth else now.month endDay = 1 - debug.debug("calculate end day of month") + logger.debug("calculate end day of month") lockedYear = lockedYear if lockedMonth != 12 else (lockedYear + 1) lockedMonth = (lockedMonth + 1) if lockedMonth != 12 else 1 for i in range(1, 8): @@ -86,33 +79,33 @@ class MainController(mainJobKindController.Base, monthLockedEndDate = datetime( lockedYear, lockedMonth, endDay) - timedelta(1) - debug.debug("get lock day from database") + logger.debug("get lock day from database") retVal = db.getLockedDay(date.date()) if not retVal: - debug.debug( + logger.debug( "lock day not exists, retVal is {{ {} }}".format(retVal)) if date.date() <= monthLockedEndDate.date(): - debug.debug("lock day {{ {} }}".format(date.date())) + logger.debug("lock day {{ {} }}".format(date.date())) self.setLockedDay(date, True) retVal = db.getLockedDay(date.date()) else: retVal = {"daydate": date.date(), "locked": False} - debug.debug("locked day is {{ {} }}".format(retVal)) + logger.debug("locked day is {{ {} }}".format(retVal)) return retVal def __updateDataFromLDAP(self, user): - debug.info("update data from ldap for user {{ {} }}".format(user)) + logger.info("update data from ldap for user {{ {} }}".format(user)) groups = ldap.getGroup(user.uid) - debug.debug("ldap gorups are {{ {} }}".format(groups)) + logger.debug("ldap gorups are {{ {} }}".format(groups)) user_data = ldap.getUserData(user.uid) - debug.debug("ldap data is {{ {} }}".format(user_data)) + logger.debug("ldap data is {{ {} }}".format(user_data)) user_data['gruppe'] = groups user_data['group'] = groups user.updateData(user_data) db.updateUser(user) def checkBarUser(self, user): - debug.info("check if user {{ {} }} is baruser") + logger.info("check if user {{ {} }} is baruser") date = datetime.now() zero = date.replace(hour=0, minute=0, second=0, microsecond=0) end = zero + timedelta(hours=12) @@ -121,22 +114,22 @@ class MainController(mainJobKindController.Base, if date > zero and end > date: startdatetime = startdatetime - timedelta(days=1) enddatetime = startdatetime + timedelta(days=1) - debug.debug("startdatetime is {{ {} }} and enddatetime is {{ {} }}".format( + logger.debug("startdatetime is {{ {} }} and enddatetime is {{ {} }}".format( startdatetime, end)) result = False if date >= startdatetime and date < enddatetime: result = db.getWorker(user, startdatetime) - debug.debug("worker is {{ {} }}".format(result)) + logger.debug("worker is {{ {} }}".format(result)) return True if result else False def sendMail(self, username): - debug.info("send mail to user {{ {} }}".format(username)) + logger.info("send mail to user {{ {} }}".format(username)) if type(username) == User: user = username if type(username) == str: user = db.getUser(username) retVal = emailController.sendMail(user) - debug.debug("send mail is {{ {} }}".format(retVal)) + logger.debug("send mail is {{ {} }}".format(retVal)) return retVal def sendAllMail(self): diff --git a/flaschengeist/system/controller/mainController/mainUserController.py b/flaschengeist/system/controller/mainController/mainUserController.py index 135663d..1f553fa 100644 --- a/flaschengeist/system/controller/mainController/mainUserController.py +++ b/flaschengeist/system/controller/mainController/mainUserController.py @@ -1,14 +1,10 @@ -from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError +from flaschengeist.system.exceptions import UsernameExistLDAP, LDAPExcetpion, PermissionDenied +from flaschengeist.system.models.user import User +from flaschengeist.system.database import db -from geruecht.exceptions import UsernameExistLDAP, LDAPExcetpion, PermissionDenied -import geruecht.controller.databaseController as dc -import geruecht.controller.ldapController as lc -from geruecht.logger import getDebugLogger -from geruecht.model.user import User - -db = dc.DatabaseController() -ldap = lc.LDAPController() -debug = getDebugLogger() +from flask import Blueprint, current_app +from werkzeug.local import LocalProxy +logger = LocalProxy(lambda: current_app.logger) class Base: def getAllStatus(self): @@ -167,13 +163,14 @@ class Base: ldap.login(username, password) def loginUser(self, username, password): - debug.info("login user {{ {} }}".format(username)) - try: - user = self.getUser(username) - debug.debug("user is {{ {} }}".format(user)) - user.password = password - ldap.login(username, password) - return user - except PermissionDenied as err: - debug.debug("permission is denied", exc_info=True) - raise err \ No newline at end of file + logger.info("login user {{ {} }}".format(username)) + user = User.query.filter_by(uid=username).first() + if user is None: + user = User(uid=username) + for backend in current_app.config['FG_AUTH_BACKENDS']: + b = backend() + if b.login(user, password): + db.session.add(user) + db.session.commit() + return user + raise PermissionDenied() diff --git a/flaschengeist/system/decorator.py b/flaschengeist/system/decorator.py index d948a50..4c67c0f 100644 --- a/flaschengeist/system/decorator.py +++ b/flaschengeist/system/decorator.py @@ -1,35 +1,29 @@ from functools import wraps -from flask import current_app -from werkzeug.local import LocalProxy -logger = LocalProxy(lambda: current_app.logger) - +from flask import current_app, request, jsonify +from flaschengeist import logger def login_required(**kwargs): - import geruecht.controller.accesTokenController as ac - from geruecht.model import BAR, USER, MONEY, GASTRO, VORSTAND, EXTERN - from flask import request, jsonify - accessController = ac.AccesTokenController() - groups = [USER, BAR, GASTRO, MONEY, VORSTAND, EXTERN] - bar = False - if "groups" in kwargs: - groups = kwargs["groups"] - if "bar" in kwargs: - bar = kwargs["bar"] - logger.debug("groups are {{ {} }}".format(groups)) + from .controller.accesTokenController import AccesTokenController + accessController = AccesTokenController() + #if "groups" in kwargs: + # groups = kwargs["groups"] + #if "bar" in kwargs: + # bar = kwargs["bar"] + #logger.debug("groups are {{ {} }}".format(groups)) def real_decorator(func): @wraps(func) def wrapper(*args, **kwargs): token = request.headers.get('Token') logger.debug("token is {{ {} }}".format(token)) - accToken = accessController.validateAccessToken(token, groups) + accToken = accessController.validateAccessToken(token, None) logger.debug("accToken is {{ {} }}".format(accToken)) kwargs['accToken'] = accToken if accToken: logger.debug("token {{ {} }} is valid".format(token)) - if accToken.lock_bar and not bar: - return jsonify({"error": "error", - "message": "permission forbidden"}), 403 + # if accToken.lock_bar and not bar: + # return jsonify({"error": "error", + # "message": "permission forbidden"}), 403 return func(*args, **kwargs) else: logger.warning("token {{ {} }} is not valid".format(token)) diff --git a/flaschengeist/system/models/accessToken.py b/flaschengeist/system/models/accessToken.py index fc19e96..e9a3546 100644 --- a/flaschengeist/system/models/accessToken.py +++ b/flaschengeist/system/models/accessToken.py @@ -52,7 +52,7 @@ class AccessToken(db.Model): return dic def __eq__(self, token): - return True if self.token == token else False + return self.token == token def __sub__(self, other): return other - self.timestamp diff --git a/flaschengeist/system/models/user.py b/flaschengeist/system/models/user.py index 8677f5b..17d2ad3 100644 --- a/flaschengeist/system/models/user.py +++ b/flaschengeist/system/models/user.py @@ -48,6 +48,16 @@ class User(db.Model): if 'displayname' in data: self.displayname = data['displayname'] + def toJSON(self): + return { + "uid": self.uid, + "displayname": self.displayname, + "firstname": self.firstname, + "lastname": self.lastname, + "mail": self.mail, + "groups": self.groups + } + class UserAttribute(db.Model): __tablename__ = 'userAttribute' diff --git a/setup.py b/setup.py index 50a8de7..b16c5c4 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( install_requires=['Flask >= 1.1', 'PyYAML>=5.3.1', 'sqlalchemy>=1.3', "flask_sqlalchemy", "flask_ldapconn", "flask_cors"], entry_points = { 'flaschengeist.plugin': [ - 'user = flaschengeist.modules.user:register' + 'auth = flaschengeist.modules.auth:register' ], 'flaschengeist.auth': [ 'plain_auth = flaschengeist.modules.auth_plain:AuthPlain'