Fixed AccessTokenController. Fixed typos and styling.

This commit is contained in:
Ferdinand Thiessen 2020-09-02 01:09:24 +02:00
parent 66dcfa80b1
commit 3256787d64
11 changed files with 109 additions and 102 deletions

View File

@ -9,8 +9,6 @@ _modpath = Path(__file__).parent
from flask import Flask from flask import Flask
from flask_cors import CORS from flask_cors import CORS
from flask_ldapconn import LDAPConn
import ssl
import pkg_resources, yaml import pkg_resources, yaml
from logging.config import dictConfig from logging.config import dictConfig

View File

@ -10,13 +10,16 @@ from werkzeug.local import LocalProxy
from flaschengeist.system.decorator import login_required from flaschengeist.system.decorator import login_required
from flaschengeist.system.exceptions import PermissionDenied from flaschengeist.system.exceptions import PermissionDenied
from flaschengeist.system.controller import mainController as mc 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) logger = LocalProxy(lambda: current_app.logger)
accesTokenController = LocalProxy(lambda: ac.AccesTokenController()) access_controller = LocalProxy(lambda: ac.AccessTokenController())
auth_bp = Blueprint('auth', __name__) auth_bp = Blueprint('auth', __name__)
def register(): def register():
return auth_bp return auth_bp
@ -24,31 +27,14 @@ def register():
## Routes ## ## 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']) @auth_bp.route("/login", methods=['POST'])
def _login(): def _login():
""" Login User """ Login User
Nothing to say.
Login in User and create an AccessToken for the User. Login in User and create an AccessToken for the User.
Returns: 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.") logger.debug("Start log in.")
data = request.get_json() data = request.get_json()
@ -58,12 +44,12 @@ def _login():
logger.debug("username is {{ {} }}".format(username)) logger.debug("username is {{ {} }}".format(username))
try: try:
logger.debug("search {{ {} }} in database".format(username)) logger.debug("search {{ {} }} in database".format(username))
mainController = mc.MainController() main_controller = mc.MainController()
user = mainController.loginUser(username, password) user = main_controller.loginUser(username, password)
logger.debug("user is {{ {} }}".format(user)) logger.debug("user is {{ {} }}".format(user))
token = accesTokenController.createAccesToken(user, user_agent=request.user_agent) token = access_controller.create(user, user_agent=request.user_agent)
logger.debug("accesstoken is {{ {} }}".format(token)) logger.debug("access token is {{ {} }}".format(token))
logger.debug("validate accesstoken") logger.debug("validate access token")
dic = user.toJSON() dic = user.toJSON()
dic["accessToken"] = token dic["accessToken"] = token
logger.info("User {{ {} }} success login.".format(username)) logger.info("User {{ {} }} success login.".format(username))
@ -76,16 +62,34 @@ def _login():
logger.error("exception in login.", exc_info=True) logger.error("exception in login.", exc_info=True)
return jsonify({"error": "permission denied"}), 401 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("/user/getAccessTokens", methods=['GET', 'POST'])
#@auth_bp.route("/accessTokens", methods=['GET', 'POST'])
@login_required() @login_required()
def _getAccessTokens(**kwargs): def _getAccessTokens(**kwargs):
try: try:
if request.method == 'POST': if request.method == 'POST':
data = request.get_json() data = request.get_json()
accesTokenController.deleteAccessToken(accToken) token = AccessToken(data['id'], kwargs['accToken'].user, None, None, None)
delAccToken = AccessToken(data['id'], kwargs['accToken'].user, None, None, None) access_controller.delete_token(token)
accesTokenController.deleteAccessToken(delAccToken) tokens = access_controller.getAccessTokensFromUser(kwargs['accToken'].user)
tokens = accesTokenController.getAccessTokensFromUser(kwargs['accToken'].user)
r = [t.toJSON() for t in tokens] r = [t.toJSON() for t in tokens]
logger.debug("return {{ {} }}".format(r)) logger.debug("return {{ {} }}".format(r))
return jsonify(r) return jsonify(r)
@ -93,6 +97,7 @@ def _getAccessTokens(**kwargs):
logger.debug("exception", exc_info=True) logger.debug("exception", exc_info=True)
return jsonify({"error": str(err)}), 500 return jsonify({"error": str(err)}), 500
@auth_bp.route("/getLifetime", methods=['GET']) @auth_bp.route("/getLifetime", methods=['GET'])
@login_required() @login_required()
def _getLifeTime(**kwargs): def _getLifeTime(**kwargs):
@ -105,22 +110,23 @@ def _getLifeTime(**kwargs):
logger.warning("exception in get lifetime of accesstoken.", exc_info=True) logger.warning("exception in get lifetime of accesstoken.", exc_info=True)
return jsonify({"error": str(err)}), 500 return jsonify({"error": str(err)}), 500
@auth_bp.route("/setLifetime", methods=['POST']) @auth_bp.route("/setLifetime", methods=['POST'])
@login_required() @login_required()
def _saveLifeTime(**kwargs): def _saveLifeTime(**kwargs):
try: try:
accToken = kwargs['accToken'] token = kwargs['accToken']
logger.debug("save lifetime for accessToken {{ {} }}".format(accToken)) logger.debug("save lifetime for access token {{ {} }}".format(token))
data = request.get_json() data = request.get_json()
lifetime = data['value'] lifetime = data['value']
logger.debug("lifetime is {{ {} }}".format(lifetime)) logger.debug("lifetime is {{ {} }}".format(lifetime))
logger.info("set lifetime {{ {} }} to accesstoken {{ {} }}".format( logger.info("set lifetime {{ {} }} to access token {{ {} }}".format(
lifetime, accToken)) lifetime, token))
accToken.lifetime = lifetime token.lifetime = lifetime
logger.info("update accesstoken timestamp") logger.info("update access token timestamp")
accToken = accesTokenController.updateAccessToken(accToken) token = access_controller.update(token)
return jsonify({"value": accToken.lifetime }) return jsonify({"value": token.lifetime })
except Exception as err: except Exception as err:
logger.warning( 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 return jsonify({"error": str(err)}), 500

View File

@ -2,9 +2,10 @@ 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
from flask_ldapconn import LDAPConn from flask_ldapconn import LDAPConn
from ldap3 import SUBTREE#, MODIFY_REPLACE, HASHED_SALTED_MD5 from ldap3 import SUBTREE
import ssl import ssl
class AuthLDAP(modules.Auth): class AuthLDAP(modules.Auth):
_default = { _default = {
'PORT': '389', 'PORT': '389',
@ -17,14 +18,14 @@ class AuthLDAP(modules.Auth):
config[name] = self._default[name] config[name] = self._default[name]
app.config.update( app.config.update(
LDAP_SERVER = config['URL'], LDAP_SERVER=config['URL'],
LDAP_PORT = config.getint('PORT'), LDAP_PORT=config.getint('PORT'),
LDAP_BINDDN = config['BINDDN'], LDAP_BINDDN=config['BINDDN'],
LDAP_USE_TLS = False, LDAP_USE_TLS=False,
LDAP_USE_SSL = config.getboolean('USE_SSL'), LDAP_USE_SSL=config.getboolean('USE_SSL'),
LDAP_TLS_VERSION = ssl.PROTOCOL_TLSv1_2, LDAP_TLS_VERSION=ssl.PROTOCOL_TLSv1_2,
LDAP_REQUIRE_CERT = ssl.CERT_NONE, LDAP_REQUIRE_CERT=ssl.CERT_NONE,
FORCE_ATTRIBUTE_VALUE_AS_LIST = True FORCE_ATTRIBUTE_VALUE_AS_LIST=True
) )
if 'SECRET' in config: if 'SECRET' in config:
app.config['LDAP_SECRET'] = config['SECRET'], app.config['LDAP_SECRET'] = config['SECRET'],
@ -75,7 +76,9 @@ class AuthLDAP(modules.Auth):
raise Exception('Password ist falsch') raise Exception('Password ist falsch')
except Exception as err: except Exception as err:
raise Exception(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: try:
if 'username' in attributes: if 'username' in attributes:
debug.debug("change username") debug.debug("change username")
@ -83,7 +86,7 @@ class AuthLDAP(modules.Auth):
if conn.entries: if conn.entries:
debug.warning("username already exists", exc_info=True) debug.warning("username already exists", exc_info=True)
raise UsernameExistLDAP("Username already exists in LDAP") raise UsernameExistLDAP("Username already exists in LDAP")
#create modifyer # create modifyer
mody = {} mody = {}
if 'username' in attributes: if 'username' in attributes:
mody['uid'] = [(MODIFY_REPLACE, [attributes['username']])] mody['uid'] = [(MODIFY_REPLACE, [attributes['username']])]
@ -103,7 +106,8 @@ class AuthLDAP(modules.Auth):
raise LDAPExcetpion("Something went wrong in LDAP: {}".format(err)) raise LDAPExcetpion("Something went wrong in LDAP: {}".format(err))
def updateUser(self, user): 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'] r = self.ldap.connection.response[0]['attributes']
if r['uid'][0] == user.uid: if r['uid'][0] == user.uid:
user.setAttribute('DN', self.ldap.connection.response[0]['dn']) user.setAttribute('DN', self.ldap.connection.response[0]['dn'])
@ -120,15 +124,18 @@ class AuthLDAP(modules.Auth):
try: try:
groups = [] 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'] main_group_number = self.ldap.connection.response[0]['attributes']['gidNumber']
if main_group_number: if main_group_number:
if type(main_group_number) is list: if type(main_group_number) is list:
main_group_number = main_group_number[0] 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]) 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 groups_data = self.ldap.connection.response
for data in groups_data: for data in groups_data:
groups.append(data['attributes']['cn'][0]) groups.append(data['attributes']['cn'][0])

View File

@ -1,6 +1,10 @@
import hashlib, binascii, os import binascii
import hashlib
import os
import flaschengeist.modules as modules import flaschengeist.modules as modules
class AuthPlain(modules.Auth): class AuthPlain(modules.Auth):
def login(self, user, password): def login(self, user, password):
if not user: if not user:
@ -18,7 +22,6 @@ class AuthPlain(modules.Auth):
def __verify_password(self, stored_password, provided_password): def __verify_password(self, stored_password, provided_password):
salt = stored_password[:64] salt = stored_password[:64]
stored_password = stored_password[64:] stored_password = stored_password[64:]
pwdhash = hashlib.pbkdf2_hmac('sha3-512', provided_password.encode('utf-8'), pwdhash = hashlib.pbkdf2_hmac('sha3-512', provided_password.encode('utf-8'), salt.encode('ascii'), 100000)
salt.encode('ascii'), 100000)
pwdhash = binascii.hexlify(pwdhash).decode('ascii') pwdhash = binascii.hexlify(pwdhash).decode('ascii')
return pwdhash == stored_password return pwdhash == stored_password

View File

@ -14,10 +14,10 @@ default = {
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read_dict(default) config.read_dict(default)
pathes = [_modpath, Path.home()/".config"] paths = [_modpath, Path.home()/".config"]
if 'FLASCHENGEIST_CONF' in os.environ: if 'FLASCHENGEIST_CONF' in os.environ:
pathes.append(Path(os.environ.get("FLASCHENGEIST_CONF"))) paths.append(Path(os.environ.get("FLASCHENGEIST_CONF")))
for loc in pathes: for loc in paths:
try: try:
with (loc/"flaschengeist.cfg").open() as source: with (loc/"flaschengeist.cfg").open() as source:
config.read_file(source) config.read_file(source)

View File

@ -1,7 +1,7 @@
class Singleton(type): class Singleton(type):
_instances = {} _instances = {}
def __call__(cls, *args, **kwargs): def __call__(cls, *args, **kwargs):
if cls not in cls._instances: if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls] return cls._instances[cls]

View File

@ -15,7 +15,6 @@ class AccessTokenController(metaclass=Singleton):
This Class create, delete, find and manage AccesToken. This Class create, delete, find and manage AccesToken.
Attributes: Attributes:
tokenList: List of currents AccessToken
lifetime: Variable for the Lifetime of one AccessToken in seconds. lifetime: Variable for the Lifetime of one AccessToken in seconds.
""" """
instance = None instance = None
@ -29,34 +28,34 @@ class AccessTokenController(metaclass=Singleton):
logger.debug("init accesstoken controller") logger.debug("init accesstoken controller")
self.lifetime = lifetime self.lifetime = lifetime
def validateAccessToken(self, token, roles): def validate(self, token, roles):
""" Verify Accestoken """ Verify access token
Verify an Accestoken and Group so if the User has permission or not. Verify an AccessToken and Group so if the User has permission or not.
Retrieves the accestoken if valid else retrieves False Retrieves the access token if valid else retrieves False
Args: Args:
token: Token to verify. token: Token to verify.
roles: Roles needed to access restricted routes roles: Roles needed to access restricted routes
Returns: 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)) logger.debug("check token {{ {} }} is valid".format(token))
for accToken in AccessToken.query.filter_by(token=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() now = datetime.utcnow()
logger.debug("now is {{ {} }}, endtime is {{ {} }}".format(now, endTime)) logger.debug("now is {{ {} }}, endtime is {{ {} }}".format(now, time_end))
if now <= endTime: if now <= time_end:
logger.debug("check if token {{ {} }} is same as {{ {} }}".format(token, accToken)) logger.debug("check if token {{ {} }} is same as {{ {} }}".format(token, accToken))
if not roles or (roles and self.userHasRole(accToken.user, roles)): if not roles or (roles and self.userHasRole(accToken.user, roles)):
accToken.updateTimestamp() accToken.updateTimestamp()
db.session.commit() db.session.commit()
return accToken return accToken
else: 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.delete(accToken)
db.session.commit() 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 return False
def userHasRole(self, user, roles): def userHasRole(self, user, roles):
@ -66,24 +65,25 @@ class AccessTokenController(metaclass=Singleton):
return True return True
return False return False
def createAccesToken(self, user, user_agent=None): def create(self, user, user_agent=None):
""" Create an AccessToken """ Create an AccessToken
Create an AccessToken for an User and add it to the tokenList. Create an AccessToken for an User and add it to the tokenList.
Args: 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: Returns:
A created Token for User A created Token for User
""" """
logger.debug("creat accesstoken") logger.debug("create access token")
token = secrets.token_hex(16) token_str = secrets.token_hex(16)
accToken = AccessToken(token=token, user=user, lifetime=self.lifetime, browser=user_agent.browser, platform=user_agent.platform) token = AccessToken(token=token_str, user=user, lifetime=self.lifetime, browser=user_agent.browser, platform=user_agent.platform)
db.session.add(accToken) db.session.add(token)
db.session.commit() db.session.commit()
logger.debug("accesstoken is {{ {} }}".format(accToken)) logger.debug("access token is {{ {} }}".format(token))
return token return token
def getAccessTokensFromUser(self, user): def getAccessTokensFromUser(self, user):
@ -96,12 +96,12 @@ class AccessTokenController(metaclass=Singleton):
AccessToken.query.filter_by(token=accessToken).delete() AccessToken.query.filter_by(token=accessToken).delete()
db.session.commit() db.session.commit()
def updateAccessToken(self, accessToken): @staticmethod
accessToken.updateTimestamp() def update_token(self, token):
token.updateTimestamp()
db.session.commit() db.session.commit()
return accessToken
def clearExpired(self): def clear_expired(self):
logger.debug("Clear expired AccessToken") logger.debug("Clear expired AccessToken")
mightExpired = datetime.utcnow() - timedelta(seconds=self.lifetime) mightExpired = datetime.utcnow() - timedelta(seconds=self.lifetime)
tokens = AccessToken.query.filter(AccessToken.timestamp < mightExpired) tokens = AccessToken.query.filter(AccessToken.timestamp < mightExpired)

View File

@ -1,10 +1,11 @@
from functools import wraps from functools import wraps
from flask import current_app, request, jsonify from flask import request, jsonify
from flaschengeist import logger from flaschengeist import logger
def login_required(**kwargs): def login_required(**kwargs):
from .controller.accessTokenController import AccessTokenController from .controller.accessTokenController import AccessTokenController
accessController = AccessTokenController() ac_controller = AccessTokenController()
roles = None roles = None
if "roles" in kwargs: if "roles" in kwargs:
roles = kwargs["roles"] roles = kwargs["roles"]
@ -14,10 +15,10 @@ def login_required(**kwargs):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
token = request.headers.get('Token') token = request.headers.get('Token')
logger.debug("token is {{ {} }}".format(token)) logger.debug("token is {{ {} }}".format(token))
accToken = accessController.validateAccessToken(token, roles) access_token = ac_controller.validate(token, roles)
logger.debug("accToken is {{ {} }}".format(accToken)) logger.debug("accToken is {{ {} }}".format(access_token))
kwargs['accToken'] = accToken kwargs['accToken'] = access_token
if accToken: if access_token:
logger.debug("token {{ {} }} is valid".format(token)) logger.debug("token {{ {} }} is valid".format(token))
return func(*args, **kwargs) return func(*args, **kwargs)
else: else:

View File

@ -1,14 +1,6 @@
class PermissionDenied(Exception): class PermissionDenied(Exception):
pass pass
class UsernameExistDB(Exception): class UsernameExistDB(Exception):
pass pass
class UsernameExistLDAP(Exception):
pass
class DatabaseExecption(Exception):
pass
class LDAPExcetpion(Exception):
pass
class DayLocked(Exception):
pass
class TansactJobIsAnswerdException(Exception):
pass

View File

@ -6,6 +6,7 @@ from secrets import compare_digest
logger = LocalProxy(lambda: current_app.logger) logger = LocalProxy(lambda: current_app.logger)
class AccessToken(db.Model): class AccessToken(db.Model):
""" Model for an AccessToken """ Model for an AccessToken

View File

@ -1,6 +1,4 @@
from datetime import datetime
from ..database import db from ..database import db
from .accessToken import AccessToken
from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import attribute_mapped_collection
from flask import current_app from flask import current_app
from werkzeug.local import LocalProxy 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')) db.Column('group_id', db.Integer, db.ForeignKey('group.id'))
) )
class User(db.Model): class User(db.Model):
""" Database Object for User """ Database Object for User