From 3256787d642e2dbbbe54b74adb75fa333c626990 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Wed, 2 Sep 2020 01:09:24 +0200 Subject: [PATCH] Fixed AccessTokenController. Fixed typos and styling. --- flaschengeist/__init__.py | 2 - flaschengeist/modules/auth/__init__.py | 82 ++++++++++--------- flaschengeist/modules/auth_ldap/__init__.py | 37 +++++---- flaschengeist/modules/auth_plain/__init__.py | 9 +- flaschengeist/system/config.py | 6 +- flaschengeist/system/controller/__init__.py | 2 +- .../controller/accessTokenController.py | 44 +++++----- flaschengeist/system/decorator.py | 13 +-- flaschengeist/system/exceptions/__init__.py | 12 +-- flaschengeist/system/models/accessToken.py | 1 + flaschengeist/system/models/user.py | 3 +- 11 files changed, 109 insertions(+), 102 deletions(-) diff --git a/flaschengeist/__init__.py b/flaschengeist/__init__.py index 2d79f16..0ea570e 100644 --- a/flaschengeist/__init__.py +++ b/flaschengeist/__init__.py @@ -9,8 +9,6 @@ _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 diff --git a/flaschengeist/modules/auth/__init__.py b/flaschengeist/modules/auth/__init__.py index bfd7f21..5e867ae 100644 --- a/flaschengeist/modules/auth/__init__.py +++ b/flaschengeist/modules/auth/__init__.py @@ -10,13 +10,16 @@ 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 +import flaschengeist.system.controller.accessTokenController as ac + +from flaschengeist.system.models.accessToken import AccessToken logger = LocalProxy(lambda: current_app.logger) -accesTokenController = LocalProxy(lambda: ac.AccesTokenController()) +access_controller = LocalProxy(lambda: ac.AccessTokenController()) auth_bp = Blueprint('auth', __name__) + def register(): return auth_bp @@ -24,31 +27,14 @@ def register(): ## 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) - accesTokenController.clearExpired() - 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 + A JSON-File with user information and created token or errors """ logger.debug("Start log in.") data = request.get_json() @@ -58,12 +44,12 @@ def _login(): logger.debug("username is {{ {} }}".format(username)) try: logger.debug("search {{ {} }} in database".format(username)) - mainController = mc.MainController() - user = mainController.loginUser(username, password) + main_controller = mc.MainController() + user = main_controller.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") + token = access_controller.create(user, user_agent=request.user_agent) + logger.debug("access token is {{ {} }}".format(token)) + logger.debug("validate access token") dic = user.toJSON() dic["accessToken"] = token logger.info("User {{ {} }} success login.".format(username)) @@ -76,16 +62,34 @@ def _login(): logger.error("exception in login.", exc_info=True) return jsonify({"error": "permission denied"}), 401 + +@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") + access_controller.deleteAccessToken(accToken) + access_controller.clearExpired() + 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("/user/getAccessTokens", methods=['GET', 'POST']) +#@auth_bp.route("/accessTokens", methods=['GET', 'POST']) @login_required() def _getAccessTokens(**kwargs): try: if request.method == 'POST': data = request.get_json() - accesTokenController.deleteAccessToken(accToken) - delAccToken = AccessToken(data['id'], kwargs['accToken'].user, None, None, None) - accesTokenController.deleteAccessToken(delAccToken) - tokens = accesTokenController.getAccessTokensFromUser(kwargs['accToken'].user) + token = AccessToken(data['id'], kwargs['accToken'].user, None, None, None) + access_controller.delete_token(token) + tokens = access_controller.getAccessTokensFromUser(kwargs['accToken'].user) r = [t.toJSON() for t in tokens] logger.debug("return {{ {} }}".format(r)) return jsonify(r) @@ -93,6 +97,7 @@ def _getAccessTokens(**kwargs): logger.debug("exception", exc_info=True) return jsonify({"error": str(err)}), 500 + @auth_bp.route("/getLifetime", methods=['GET']) @login_required() def _getLifeTime(**kwargs): @@ -105,22 +110,23 @@ def _getLifeTime(**kwargs): logger.warning("exception in get lifetime of accesstoken.", exc_info=True) return jsonify({"error": str(err)}), 500 + @auth_bp.route("/setLifetime", methods=['POST']) @login_required() def _saveLifeTime(**kwargs): try: - accToken = kwargs['accToken'] - logger.debug("save lifetime for accessToken {{ {} }}".format(accToken)) + token = kwargs['accToken'] + logger.debug("save lifetime for access token {{ {} }}".format(token)) data = request.get_json() lifetime = data['value'] logger.debug("lifetime is {{ {} }}".format(lifetime)) - logger.info("set lifetime {{ {} }} to accesstoken {{ {} }}".format( - lifetime, accToken)) - accToken.lifetime = lifetime - logger.info("update accesstoken timestamp") - accToken = accesTokenController.updateAccessToken(accToken) - return jsonify({"value": accToken.lifetime }) + logger.info("set lifetime {{ {} }} to access token {{ {} }}".format( + lifetime, token)) + token.lifetime = lifetime + logger.info("update access token timestamp") + token = access_controller.update(token) + return jsonify({"value": token.lifetime }) except Exception as err: logger.warning( - "exception in save lifetime for accesstoken.", exc_info=True) + "exception in save lifetime for access token.", exc_info=True) return jsonify({"error": str(err)}), 500 diff --git a/flaschengeist/modules/auth_ldap/__init__.py b/flaschengeist/modules/auth_ldap/__init__.py index 7da6d10..00e5803 100644 --- a/flaschengeist/modules/auth_ldap/__init__.py +++ b/flaschengeist/modules/auth_ldap/__init__.py @@ -2,9 +2,10 @@ import flaschengeist.modules as modules from flaschengeist import logger from flask import current_app as app from flask_ldapconn import LDAPConn -from ldap3 import SUBTREE#, MODIFY_REPLACE, HASHED_SALTED_MD5 +from ldap3 import SUBTREE import ssl + class AuthLDAP(modules.Auth): _default = { 'PORT': '389', @@ -17,14 +18,14 @@ class AuthLDAP(modules.Auth): config[name] = self._default[name] app.config.update( - LDAP_SERVER = config['URL'], - LDAP_PORT = config.getint('PORT'), - LDAP_BINDDN = config['BINDDN'], - LDAP_USE_TLS = False, - LDAP_USE_SSL = config.getboolean('USE_SSL'), - LDAP_TLS_VERSION = ssl.PROTOCOL_TLSv1_2, - LDAP_REQUIRE_CERT = ssl.CERT_NONE, - FORCE_ATTRIBUTE_VALUE_AS_LIST = True + LDAP_SERVER=config['URL'], + LDAP_PORT=config.getint('PORT'), + LDAP_BINDDN=config['BINDDN'], + LDAP_USE_TLS=False, + LDAP_USE_SSL=config.getboolean('USE_SSL'), + LDAP_TLS_VERSION=ssl.PROTOCOL_TLSv1_2, + LDAP_REQUIRE_CERT=ssl.CERT_NONE, + FORCE_ATTRIBUTE_VALUE_AS_LIST=True ) if 'SECRET' in config: app.config['LDAP_SECRET'] = config['SECRET'], @@ -75,7 +76,9 @@ class AuthLDAP(modules.Auth): raise Exception('Password ist falsch') except Exception as err: raise Exception(err) - debug.info("modify ldap data from user {{ {} }} with attributes (can't show because here can be a password)".format(user)) + debug.info( + "modify ldap data from user {{ {} }} with attributes (can't show because here can be a password)".format( + user)) try: if 'username' in attributes: debug.debug("change username") @@ -83,7 +86,7 @@ class AuthLDAP(modules.Auth): if conn.entries: debug.warning("username already exists", exc_info=True) raise UsernameExistLDAP("Username already exists in LDAP") - #create modifyer + # create modifyer mody = {} if 'username' in attributes: mody['uid'] = [(MODIFY_REPLACE, [attributes['username']])] @@ -103,7 +106,8 @@ class AuthLDAP(modules.Auth): raise LDAPExcetpion("Something went wrong in LDAP: {}".format(err)) def updateUser(self, user): - self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(user.uid), SUBTREE, attributes=['uid', 'givenName', 'sn', 'mail']) + 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'] if r['uid'][0] == user.uid: user.setAttribute('DN', self.ldap.connection.response[0]['dn']) @@ -120,15 +124,18 @@ class AuthLDAP(modules.Auth): try: groups = [] - self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(uid), SUBTREE, attributes=['gidNumber']) + self.ldap.connection.search('ou=user,{}'.format(self.dn), '(uid={})'.format(uid), SUBTREE, + attributes=['gidNumber']) main_group_number = self.ldap.connection.response[0]['attributes']['gidNumber'] if main_group_number: if type(main_group_number) is list: main_group_number = main_group_number[0] - self.ldap.connection.search('ou=group,{}'.format(self.dn), '(gidNumber={})'.format(main_group_number), attributes=['cn']) + self.ldap.connection.search('ou=group,{}'.format(self.dn), '(gidNumber={})'.format(main_group_number), + attributes=['cn']) groups.append(self.ldap.connection.response[0]['attributes']['cn'][0]) - self.ldap.connection.search('ou=group,{}'.format(self.dn), '(memberUID={})'.format(uid), SUBTREE, attributes=['cn']) + self.ldap.connection.search('ou=group,{}'.format(self.dn), '(memberUID={})'.format(uid), SUBTREE, + attributes=['cn']) groups_data = self.ldap.connection.response for data in groups_data: groups.append(data['attributes']['cn'][0]) diff --git a/flaschengeist/modules/auth_plain/__init__.py b/flaschengeist/modules/auth_plain/__init__.py index 037533c..908c642 100644 --- a/flaschengeist/modules/auth_plain/__init__.py +++ b/flaschengeist/modules/auth_plain/__init__.py @@ -1,6 +1,10 @@ -import hashlib, binascii, os +import binascii +import hashlib +import os + import flaschengeist.modules as modules + class AuthPlain(modules.Auth): def login(self, user, password): if not user: @@ -18,7 +22,6 @@ class AuthPlain(modules.Auth): 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 = 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/system/config.py b/flaschengeist/system/config.py index 1ba87c5..a83d6c0 100644 --- a/flaschengeist/system/config.py +++ b/flaschengeist/system/config.py @@ -14,10 +14,10 @@ default = { config = configparser.ConfigParser() config.read_dict(default) -pathes = [_modpath, Path.home()/".config"] +paths = [_modpath, Path.home()/".config"] if 'FLASCHENGEIST_CONF' in os.environ: - pathes.append(Path(os.environ.get("FLASCHENGEIST_CONF"))) -for loc in pathes: + paths.append(Path(os.environ.get("FLASCHENGEIST_CONF"))) +for loc in paths: try: with (loc/"flaschengeist.cfg").open() as source: config.read_file(source) diff --git a/flaschengeist/system/controller/__init__.py b/flaschengeist/system/controller/__init__.py index 83ca20c..3776cb9 100644 --- a/flaschengeist/system/controller/__init__.py +++ b/flaschengeist/system/controller/__init__.py @@ -1,7 +1,7 @@ class Singleton(type): _instances = {} + def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] - diff --git a/flaschengeist/system/controller/accessTokenController.py b/flaschengeist/system/controller/accessTokenController.py index 15b503d..033d383 100644 --- a/flaschengeist/system/controller/accessTokenController.py +++ b/flaschengeist/system/controller/accessTokenController.py @@ -15,7 +15,6 @@ class AccessTokenController(metaclass=Singleton): This Class create, delete, find and manage AccesToken. Attributes: - tokenList: List of currents AccessToken lifetime: Variable for the Lifetime of one AccessToken in seconds. """ instance = None @@ -29,34 +28,34 @@ class AccessTokenController(metaclass=Singleton): logger.debug("init accesstoken controller") self.lifetime = lifetime - def validateAccessToken(self, token, roles): - """ Verify Accestoken + def validate(self, token, roles): + """ Verify access token - Verify an Accestoken and Group so if the User has permission or not. - Retrieves the accestoken if valid else retrieves False + Verify an AccessToken and Group so if the User has permission or not. + Retrieves the access token if valid else retrieves False Args: token: Token to verify. roles: Roles needed to access restricted routes Returns: - An the AccesToken for this given Token or False. + An the AccessToken for this given Token or False. """ logger.debug("check token {{ {} }} is valid".format(token)) for accToken in AccessToken.query.filter_by(token=token): - endTime = accToken.timestamp + timedelta(seconds=accToken.lifetime) + time_end = accToken.timestamp + timedelta(seconds=accToken.lifetime) now = datetime.utcnow() - logger.debug("now is {{ {} }}, endtime is {{ {} }}".format(now, endTime)) - if now <= endTime: + logger.debug("now is {{ {} }}, endtime is {{ {} }}".format(now, time_end)) + if now <= time_end: logger.debug("check if token {{ {} }} is same as {{ {} }}".format(token, accToken)) if not roles or (roles and self.userHasRole(accToken.user, roles)): accToken.updateTimestamp() db.session.commit() return accToken else: - logger.debug("accesstoken is {{ {} }} out of date".format(accToken)) + logger.debug("access token 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)) + logger.debug("no valid access token with token: {{ {} }} and group: {{ {} }}".format(token, roles)) return False def userHasRole(self, user, roles): @@ -66,24 +65,25 @@ class AccessTokenController(metaclass=Singleton): return True return False - def createAccesToken(self, user, user_agent=None): + def create(self, user, user_agent=None): """ Create an AccessToken Create an AccessToken for an User and add it to the tokenList. Args: - user: For wich User is to create an AccessToken + user: For which User is to create an AccessToken + user_agent: User agent to identify session Returns: A created Token for User """ - 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) + logger.debug("create access token") + token_str = secrets.token_hex(16) + token = AccessToken(token=token_str, user=user, lifetime=self.lifetime, browser=user_agent.browser, platform=user_agent.platform) + db.session.add(token) db.session.commit() - logger.debug("accesstoken is {{ {} }}".format(accToken)) + logger.debug("access token is {{ {} }}".format(token)) return token def getAccessTokensFromUser(self, user): @@ -96,12 +96,12 @@ class AccessTokenController(metaclass=Singleton): AccessToken.query.filter_by(token=accessToken).delete() db.session.commit() - def updateAccessToken(self, accessToken): - accessToken.updateTimestamp() + @staticmethod + def update_token(self, token): + token.updateTimestamp() db.session.commit() - return accessToken - def clearExpired(self): + def clear_expired(self): logger.debug("Clear expired AccessToken") mightExpired = datetime.utcnow() - timedelta(seconds=self.lifetime) tokens = AccessToken.query.filter(AccessToken.timestamp < mightExpired) diff --git a/flaschengeist/system/decorator.py b/flaschengeist/system/decorator.py index 26a1e96..a428e10 100644 --- a/flaschengeist/system/decorator.py +++ b/flaschengeist/system/decorator.py @@ -1,10 +1,11 @@ from functools import wraps -from flask import current_app, request, jsonify +from flask import request, jsonify from flaschengeist import logger + def login_required(**kwargs): from .controller.accessTokenController import AccessTokenController - accessController = AccessTokenController() + ac_controller = AccessTokenController() roles = None if "roles" in kwargs: roles = kwargs["roles"] @@ -14,10 +15,10 @@ def login_required(**kwargs): def wrapper(*args, **kwargs): token = request.headers.get('Token') logger.debug("token is {{ {} }}".format(token)) - accToken = accessController.validateAccessToken(token, roles) - logger.debug("accToken is {{ {} }}".format(accToken)) - kwargs['accToken'] = accToken - if accToken: + access_token = ac_controller.validate(token, roles) + logger.debug("accToken is {{ {} }}".format(access_token)) + kwargs['accToken'] = access_token + if access_token: logger.debug("token {{ {} }} is valid".format(token)) return func(*args, **kwargs) else: diff --git a/flaschengeist/system/exceptions/__init__.py b/flaschengeist/system/exceptions/__init__.py index 307c48e..4d18a6b 100644 --- a/flaschengeist/system/exceptions/__init__.py +++ b/flaschengeist/system/exceptions/__init__.py @@ -1,14 +1,6 @@ class PermissionDenied(Exception): pass + + class UsernameExistDB(Exception): pass -class UsernameExistLDAP(Exception): - pass -class DatabaseExecption(Exception): - pass -class LDAPExcetpion(Exception): - pass -class DayLocked(Exception): - pass -class TansactJobIsAnswerdException(Exception): - pass \ No newline at end of file diff --git a/flaschengeist/system/models/accessToken.py b/flaschengeist/system/models/accessToken.py index 396c5ad..3d9fac8 100644 --- a/flaschengeist/system/models/accessToken.py +++ b/flaschengeist/system/models/accessToken.py @@ -6,6 +6,7 @@ from secrets import compare_digest logger = LocalProxy(lambda: current_app.logger) + class AccessToken(db.Model): """ Model for an AccessToken diff --git a/flaschengeist/system/models/user.py b/flaschengeist/system/models/user.py index f462595..4844c70 100644 --- a/flaschengeist/system/models/user.py +++ b/flaschengeist/system/models/user.py @@ -1,6 +1,4 @@ -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 @@ -11,6 +9,7 @@ association_table = db.Table('user_group', db.Column('group_id', db.Integer, db.ForeignKey('group.id')) ) + class User(db.Model): """ Database Object for User