mirror of
https://github.com/yweber/lodel2.git
synced 2025-11-02 04:20:55 +01:00
Merge branch 'newlodel' of git.labocleo.org:lodel2 into newlodel
This commit is contained in:
commit
f0f1ed9a83
7 changed files with 146 additions and 78 deletions
|
|
@ -121,31 +121,20 @@ a session is allready started !!!")
|
||||||
#@todo Maybe we can delete this metaclass....
|
#@todo Maybe we can delete this metaclass....
|
||||||
class ClientMetaclass(type):
|
class ClientMetaclass(type):
|
||||||
|
|
||||||
SESSION_ID_NAME = '__SESSION_ID__'
|
|
||||||
|
|
||||||
def __init__(self, name, bases, attrs):
|
def __init__(self, name, bases, attrs):
|
||||||
self.__session = dict()
|
|
||||||
return super(ClientMetaclass, self).__init__(name, bases, attrs)
|
return super(ClientMetaclass, self).__init__(name, bases, attrs)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if key not in self.__session:
|
return self.session()[key]
|
||||||
raise KeyError("This client instance does not have a '%s' data" % key)
|
|
||||||
return self.__session[key]
|
def __delitem__(self, key):
|
||||||
|
del(self.session()[key])
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
if SESSION_ID_NAME not in self.__session:
|
self.session()[key] = value
|
||||||
self.__session[SESSION_ID_NAME] = SessionHandler.start_session()
|
|
||||||
self.__session[key] = value
|
|
||||||
|
|
||||||
def __token(self):
|
|
||||||
return None if SESSION_ID_NAME not in self.__sessions else self.__session[SESSION_ID_NAME]
|
|
||||||
|
|
||||||
##@brief Return a copy of sessions infos
|
|
||||||
def session_dump(self):
|
|
||||||
#first set all sessions values
|
|
||||||
SessionHandler.save_session(self.__session)
|
|
||||||
return copy.copy(self.__session)
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self._instance)
|
||||||
|
|
||||||
##@brief Abstract singleton class designed to handle client informations
|
##@brief Abstract singleton class designed to handle client informations
|
||||||
#
|
#
|
||||||
|
|
@ -196,20 +185,6 @@ class Client(object, metaclass = ClientMetaclass):
|
||||||
self.__session = LodelSession(session_token)
|
self.__session = LodelSession(session_token)
|
||||||
logger.debug("New client : %s" % self)
|
logger.debug("New client : %s" % self)
|
||||||
|
|
||||||
##@brief Attempt to restore a session given a session token
|
|
||||||
#@param token mixed : a session token
|
|
||||||
#@return Session datas (a dict)
|
|
||||||
#@throw ClientAuthenticationFailure if token is not valid or not
|
|
||||||
#existing
|
|
||||||
def _restore_session(self, token):
|
|
||||||
return self.__session.restore(token)
|
|
||||||
|
|
||||||
##@brief Return the current session token or None
|
|
||||||
#@return A session token or None
|
|
||||||
@classmethod
|
|
||||||
def session_token(cls):
|
|
||||||
return self.__session.retrieve_token()
|
|
||||||
|
|
||||||
##@brief Try to authenticate a user with a login and a password
|
##@brief Try to authenticate a user with a login and a password
|
||||||
#@param login str : provided login
|
#@param login str : provided login
|
||||||
#@param password str : provided password (hash)
|
#@param password str : provided password (hash)
|
||||||
|
|
@ -245,6 +220,28 @@ class Client(object, metaclass = ClientMetaclass):
|
||||||
break
|
break
|
||||||
if self.is_anon():
|
if self.is_anon():
|
||||||
self.fail() #Security logging
|
self.fail() #Security logging
|
||||||
|
|
||||||
|
##@brief Attempt to restore a session given a session token
|
||||||
|
#@param token mixed : a session token
|
||||||
|
#@return Session datas (a dict)
|
||||||
|
#@throw ClientAuthenticationFailure if token is not valid or not
|
||||||
|
#existing
|
||||||
|
@classmethod
|
||||||
|
def restore_session(self, token):
|
||||||
|
cls._assert_instance()
|
||||||
|
return self.__session.restore(token)
|
||||||
|
|
||||||
|
##@brief Return the current session token or None
|
||||||
|
#@return A session token or None
|
||||||
|
@classmethod
|
||||||
|
def session_token(cls):
|
||||||
|
cls._assert_instance()
|
||||||
|
return cls._instance.__session.retrieve_token()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def session(cls):
|
||||||
|
cls._assert_instance()
|
||||||
|
return cls._instance.__session
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def destroy(cls):
|
def destroy(cls):
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
from lodel import logger
|
from lodel import logger
|
||||||
|
from lodel.plugin.hooks import LodelHook
|
||||||
|
|
||||||
##@brief Handles common errors with a Client
|
##@brief Handles common errors with a Client
|
||||||
class ClientError(Exception):
|
class ClientError(Exception):
|
||||||
##@brief The logger function to use to log the error message
|
##@brief The logger function to use to log the error message
|
||||||
_loglvl = logger.warning
|
_loglvl = 'warning'
|
||||||
##@brief Error str
|
##@brief Error str
|
||||||
_err_str = "Error"
|
_err_str = "Error"
|
||||||
##@brief the hook name to trigger with error
|
##@brief the hook name to trigger with error
|
||||||
|
|
@ -17,8 +18,9 @@ class ClientError(Exception):
|
||||||
#"<client infos> : <_err_str>[ : <msg>]"
|
#"<client infos> : <_err_str>[ : <msg>]"
|
||||||
def __init__(self, client, msg = ""):
|
def __init__(self, client, msg = ""):
|
||||||
msg = self.build_message(client, msg)
|
msg = self.build_message(client, msg)
|
||||||
if cls._loglvl is not None:
|
if self._loglvl is not None:
|
||||||
cls._loglvl(msg)
|
logfun = getattr(logger, self._loglvl)
|
||||||
|
logfun(msg)
|
||||||
super().__init__(msg)
|
super().__init__(msg)
|
||||||
if self._action is not None:
|
if self._action is not None:
|
||||||
LodelHook.call_hook(self._action, self, self._payload)
|
LodelHook.call_hook(self._action, self, self._payload)
|
||||||
|
|
@ -32,21 +34,21 @@ class ClientError(Exception):
|
||||||
|
|
||||||
##@brief Handles authentication failure errors
|
##@brief Handles authentication failure errors
|
||||||
class ClientAuthenticationFailure(ClientError):
|
class ClientAuthenticationFailure(ClientError):
|
||||||
_loglvl = logger.security
|
_loglvl = 'security'
|
||||||
_err_str = 'Authentication failure'
|
_err_str = 'Authentication failure'
|
||||||
_action = 'lodel2_ui_authentication_failure'
|
_action = 'lodel2_ui_authentication_failure'
|
||||||
|
|
||||||
|
|
||||||
##@brief Handles permission denied errors
|
##@brief Handles permission denied errors
|
||||||
class ClientPermissionDenied(ClientError):
|
class ClientPermissionDenied(ClientError):
|
||||||
_loglvl = logger.security
|
_loglvl = 'security'
|
||||||
_err_str = 'Permission denied'
|
_err_str = 'Permission denied'
|
||||||
_action = 'lodel2_ui_permission_denied'
|
_action = 'lodel2_ui_permission_denied'
|
||||||
|
|
||||||
|
|
||||||
##@brief Handles common errors on authentication
|
##@brief Handles common errors on authentication
|
||||||
class ClientAuthenticationError(ClientError):
|
class ClientAuthenticationError(ClientError):
|
||||||
_loglvl = logger.error
|
_loglvl = 'error'
|
||||||
_err_str = 'Authentication error'
|
_err_str = 'Authentication error'
|
||||||
_action = 'lodel2_ui_error'
|
_action = 'lodel2_ui_error'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ class DiscoverPlugin(lodel_script.LodelScript):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def argparser_config(cls, parser):
|
def argparser_config(cls, parser):
|
||||||
parser.add_argument('-d', '--directory',
|
#parser.add_argument('-d', '--directory',
|
||||||
|
parser.add_argument('PLUGIN_PATH',
|
||||||
help="Directory to walk through looking for lodel2 plugins",
|
help="Directory to walk through looking for lodel2 plugins",
|
||||||
nargs='+')
|
nargs='+')
|
||||||
parser.add_argument('-l', '--list-only', default=False,
|
parser.add_argument('-l', '--list-only', default=False,
|
||||||
|
|
@ -20,11 +21,11 @@ without modifying existing cache")
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls, args):
|
def run(cls, args):
|
||||||
from lodel.plugin.plugins import Plugin
|
from lodel.plugin.plugins import Plugin
|
||||||
if args.directory is None or len(args.directory) == 0:
|
if args.PLUGIN_PATH is None or len(args.PLUGIN_PATH) == 0:
|
||||||
cls.help_exit("Specify a least one directory")
|
cls.help_exit("Specify a least one directory")
|
||||||
no_cache = args.list_only
|
no_cache = args.list_only
|
||||||
res = Plugin.discover(args.directory, no_cache)
|
res = Plugin.discover(args.PLUGIN_PATH, no_cache)
|
||||||
print("Found plugins in : %s" % ', '.join(args.directory))
|
print("Found plugins in : %s" % ', '.join(args.PLUGIN_PATH))
|
||||||
for pname, pinfos in res['plugins'].items():
|
for pname, pinfos in res['plugins'].items():
|
||||||
print("\t- %s(%s) in %s" % (
|
print("\t- %s(%s) in %s" % (
|
||||||
pname, pinfos['version'], pinfos['path']))
|
pname, pinfos['version'], pinfos['path']))
|
||||||
|
|
|
||||||
|
|
@ -142,19 +142,23 @@ def main_run():
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
default_parser.print_help()
|
default_parser.print_help()
|
||||||
exit(1)
|
exit(1)
|
||||||
args = default_parser.parse_args()
|
|
||||||
if args.list_actions:
|
|
||||||
print("Available actions :")
|
|
||||||
for sname in sorted(__registered_scripts.keys()):
|
|
||||||
print("\t- %s" % __registered_scripts[sname])
|
|
||||||
exit(0)
|
|
||||||
#preparing sys.argv (deleting action)
|
#preparing sys.argv (deleting action)
|
||||||
action = sys.argv[1].lower()
|
action = sys.argv[1].lower()
|
||||||
del(sys.argv[1])
|
|
||||||
if action not in __registered_scripts:
|
if action not in __registered_scripts:
|
||||||
|
#Trying to parse argument with default parser
|
||||||
|
print("PASSAGE")
|
||||||
|
args = default_parser.parse_args()
|
||||||
|
if args.list_actions:
|
||||||
|
print("Available actions :")
|
||||||
|
for sname in sorted(__registered_scripts.keys()):
|
||||||
|
print("\t- %s" % __registered_scripts[sname])
|
||||||
|
exit(0)
|
||||||
|
|
||||||
print("Unknow action '%s'\n" % action, file=sys.stderr)
|
print("Unknow action '%s'\n" % action, file=sys.stderr)
|
||||||
default_parser.print_help()
|
default_parser.print_help()
|
||||||
exit(1)
|
exit(1)
|
||||||
|
#OK action is known, preparing argv to pass it to the action script
|
||||||
|
del(sys.argv[1])
|
||||||
script = __registered_scripts[action]
|
script = __registered_scripts[action]
|
||||||
ret = script._run()
|
ret = script._run()
|
||||||
ret = 0 if ret is None else ret
|
ret = 0 if ret is None else ret
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,10 @@ I'm a custom method on an instance of class %s" % self.__class__)
|
||||||
def dummy_instance_method(self):
|
def dummy_instance_method(self):
|
||||||
print("Hello world !\
|
print("Hello world !\
|
||||||
I'm a custom method on class %s" % self.__class__)
|
I'm a custom method on class %s" % self.__class__)
|
||||||
|
|
||||||
|
|
||||||
|
@LodelHook('lodel2_loader_main')
|
||||||
|
def foofun(hname, caller, payload):
|
||||||
|
from lodel import dyncode
|
||||||
|
print("Hello world ! I read dyncode from lodel.dyncode : ",
|
||||||
|
dyncode.dynclasses)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import os
|
import os
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
from lodel import logger
|
||||||
from lodel.settings import Settings
|
from lodel.settings import Settings
|
||||||
from lodel.auth.exceptions import *
|
from lodel.auth.exceptions import *
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ def destroy_session(token):
|
||||||
|
|
||||||
def restore_session(token):
|
def restore_session(token):
|
||||||
_check_token(token)
|
_check_token(token)
|
||||||
|
logger.debug("Restoring session : %s" %__sessions[token])
|
||||||
return __sessions[token]
|
return __sessions[token]
|
||||||
|
|
||||||
def save_session(token, datas):
|
def save_session(token, datas):
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,19 @@
|
||||||
import loader # Lodel2 loader
|
import loader # Lodel2 loader
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
|
||||||
from werkzeug.contrib.sessions import FilesystemSessionStore
|
from werkzeug.contrib.sessions import FilesystemSessionStore
|
||||||
from werkzeug.wrappers import Response
|
from werkzeug.wrappers import Response
|
||||||
|
from werkzeug.contrib.securecookie import SecureCookie
|
||||||
|
|
||||||
from lodel.settings import Settings
|
from lodel.settings import Settings
|
||||||
from .interface.router import get_controller
|
from .interface.router import get_controller
|
||||||
from .interface.lodelrequest import LodelRequest
|
from .interface.lodelrequest import LodelRequest
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .client import WebUiClient
|
from .client import WebUiClient
|
||||||
|
from lodel.auth.exceptions import *
|
||||||
from lodel.utils.datetime import get_utc_timestamp
|
from lodel.utils.datetime import get_utc_timestamp
|
||||||
from lodel.plugin.hooks import LodelHook
|
from lodel.plugin.hooks import LodelHook
|
||||||
|
|
||||||
|
|
@ -19,6 +24,48 @@ SESSION_EXPIRATION_LIMIT = Settings.webui.sessions.expiration
|
||||||
|
|
||||||
session_store = FilesystemSessionStore(path=SESSION_FILES_BASE_DIR, filename_template=SESSION_FILES_TEMPLATE)
|
session_store = FilesystemSessionStore(path=SESSION_FILES_BASE_DIR, filename_template=SESSION_FILES_TEMPLATE)
|
||||||
|
|
||||||
|
COOKIE_SESSION_ID = 'toktoken'
|
||||||
|
COOKIE_SESSION_HASH = 'nekotkot'
|
||||||
|
COOKIE_SESSION_HASH_SALT = [ os.urandom(32) for _ in range(2) ] #Before and after salt (maybe useless)
|
||||||
|
COOKIE_SESSION_HASH_ALGO = hashlib.sha512
|
||||||
|
|
||||||
|
##@brief Return a salted hash of a cookie
|
||||||
|
def cookie_hash(token):
|
||||||
|
return COOKIE_SESSION_HASH_ALGO(
|
||||||
|
COOKIE_SESSION_HASH_SALT[0]+token+COOKIE_SESSION_HASH_SALT[1]).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
##@brief Load cookie from request
|
||||||
|
#@note Can produce security warning logs
|
||||||
|
#@param request
|
||||||
|
#@return None or a session token
|
||||||
|
def load_cookie(request):
|
||||||
|
token = request.cookies.get(COOKIE_SESSION_ID)
|
||||||
|
if token is None and token != '':
|
||||||
|
return None
|
||||||
|
token = bytes(token, 'utf-8')
|
||||||
|
hashtok = request.cookies.get(COOKIE_SESSION_HASH)
|
||||||
|
if hashtok is None:
|
||||||
|
raise ClientAuthenticationFailure(
|
||||||
|
WebUiClient, 'Bad cookies : no hash provided')
|
||||||
|
if cookie_hash(token) != hashtok:
|
||||||
|
raise ClientAuthenticationFailure(
|
||||||
|
WebUiClient, 'Bad cookies : hash mismatch')
|
||||||
|
return token
|
||||||
|
|
||||||
|
##@brief Properly set cookies and hash given a token
|
||||||
|
#@param response
|
||||||
|
#@param token str : the session token
|
||||||
|
def save_cookie(response, token):
|
||||||
|
response.set_cookie(COOKIE_SESSION_ID, token)
|
||||||
|
response.set_cookie(COOKIE_SESSION_HASH, cookie_hash(token))
|
||||||
|
|
||||||
|
def empty_cookie(response):
|
||||||
|
response.set_cookie(COOKIE_SESSION_ID, '')
|
||||||
|
response.set_cookie(COOKIE_SESSION_HASH, '')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#Starting instance
|
#Starting instance
|
||||||
loader.start()
|
loader.start()
|
||||||
#providing access to dyncode
|
#providing access to dyncode
|
||||||
|
|
@ -49,38 +96,46 @@ def is_session_file_expired(timestamp_now, sid):
|
||||||
|
|
||||||
# WSGI Application
|
# WSGI Application
|
||||||
def application(env, start_response):
|
def application(env, start_response):
|
||||||
WebUiClient(env['REMOTE_ADDR'], env['HTTP_USER_AGENT'])
|
|
||||||
current_timestamp = get_utc_timestamp()
|
|
||||||
delete_old_session_files(current_timestamp)
|
|
||||||
request = LodelRequest(env)
|
request = LodelRequest(env)
|
||||||
sid = request.cookies.get('sid')
|
session_token = None
|
||||||
if sid is None or sid not in session_store.list():
|
|
||||||
request.session = session_store.new()
|
|
||||||
request.session['last_accessed'] = current_timestamp
|
|
||||||
else:
|
|
||||||
request.session = session_store.get(sid)
|
|
||||||
if is_session_file_expired(current_timestamp, sid):
|
|
||||||
session_store.delete(request.session)
|
|
||||||
request.session = session_store.new()
|
|
||||||
request.session['user_context'] = None
|
|
||||||
request.session['last_accessed'] = current_timestamp
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
controller = get_controller(request)
|
#We have to create the client before restoring cookie in order to be able
|
||||||
response = controller(request)
|
#to log messages with client infos
|
||||||
except HttpException as e:
|
client = WebUiClient(env['REMOTE_ADDR'], env['HTTP_USER_AGENT'], None)
|
||||||
|
session_token = load_cookie(request)
|
||||||
|
|
||||||
|
if session_token is not None:
|
||||||
|
WebClient.restore_session(token)
|
||||||
|
session_token = None
|
||||||
|
#test
|
||||||
|
WebUiClient['last_request'] = time.time()
|
||||||
try:
|
try:
|
||||||
response = e.render(request)
|
controller = get_controller(request)
|
||||||
except Exception as eb:
|
response = controller(request)
|
||||||
res = Response()
|
except HttpException as e:
|
||||||
res.status_code = 500
|
try:
|
||||||
return res
|
response = e.render(request)
|
||||||
|
except Exception as eb:
|
||||||
|
raise eb
|
||||||
if request.session.should_save:
|
res = Response()
|
||||||
session_store.save(request.session)
|
res.status_code = 500
|
||||||
response.set_cookie('sid', request.session.sid)
|
return res
|
||||||
|
session_token = WebUiClient.session_token()
|
||||||
|
if session_token is not None:
|
||||||
|
save_cookie(response,session_token)
|
||||||
|
session_token = None
|
||||||
|
|
||||||
|
|
||||||
|
except (ClientError, ClientAuthenticationError):
|
||||||
|
response = HttpException(400).render(request)
|
||||||
|
empty_cookie(response)
|
||||||
|
except ClientAuthenticationFailure:
|
||||||
|
response = HttpException(401).render(request)
|
||||||
|
empty_cookie(response)
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
res = response(env, start_response)
|
res = response(env, start_response)
|
||||||
|
|
||||||
WebUiClient.destroy()
|
WebUiClient.destroy()
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue