Introduce validation errors, authentication errors, corresponding http codes

This commit is contained in:
Alexandre Aubin 2021-03-12 03:55:52 +01:00
parent f790bde101
commit bc1bdbb247
6 changed files with 36 additions and 24 deletions

View file

@ -14,7 +14,7 @@ from importlib import import_module
from moulinette import m18n, msignals from moulinette import m18n, msignals
from moulinette.cache import open_cachefile from moulinette.cache import open_cachefile
from moulinette.globals import init_moulinette_env from moulinette.globals import init_moulinette_env
from moulinette.core import MoulinetteError, MoulinetteLock from moulinette.core import MoulinetteError, MoulinetteLock, MoulinetteAuthenticationError, MoulinetteValidationError
from moulinette.interfaces import BaseActionsMapParser, GLOBAL_SECTION, TO_RETURN_PROP from moulinette.interfaces import BaseActionsMapParser, GLOBAL_SECTION, TO_RETURN_PROP
from moulinette.utils.log import start_action_logging from moulinette.utils.log import start_action_logging
@ -207,7 +207,7 @@ class PatternParameter(_ExtraParameter):
if msg == message: if msg == message:
msg = m18n.g(message) msg = m18n.g(message)
raise MoulinetteError("invalid_argument", argument=arg_name, error=msg) raise MoulinetteValidationError("invalid_argument", argument=arg_name, error=msg)
return arg_value return arg_value
@staticmethod @staticmethod
@ -238,7 +238,7 @@ class RequiredParameter(_ExtraParameter):
def __call__(self, required, arg_name, arg_value): def __call__(self, required, arg_name, arg_value):
if required and (arg_value is None or arg_value == ""): if required and (arg_value is None or arg_value == ""):
logger.warning("argument '%s' is required", arg_name) logger.warning("argument '%s' is required", arg_name)
raise MoulinetteError("argument_required", argument=arg_name) raise MoulinetteValidationError("argument_required", argument=arg_name)
return arg_value return arg_value
@staticmethod @staticmethod
@ -497,7 +497,7 @@ class ActionsMap(object):
auth = msignals.authenticate(authenticator) auth = msignals.authenticate(authenticator)
if not auth.is_authenticated: if not auth.is_authenticated:
raise MoulinetteError("authentication_required_long") raise MoulinetteAuthenticationError("authentication_required_long")
def process(self, args, timeout=None, **kwargs): def process(self, args, timeout=None, **kwargs):
""" """

View file

@ -6,7 +6,7 @@ import hashlib
import hmac import hmac
from moulinette.cache import open_cachefile, get_cachedir, cachefile_exists from moulinette.cache import open_cachefile, get_cachedir, cachefile_exists
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError, MoulinetteAuthenticationError
logger = logging.getLogger("moulinette.authenticator") logger = logging.getLogger("moulinette.authenticator")
@ -105,7 +105,7 @@ class BaseAuthenticator(object):
self.vendor, self.vendor,
e, e,
) )
raise MoulinetteError("unable_authenticate") raise MoulinetteAuthenticationError("unable_authenticate")
self.is_authenticated = True self.is_authenticated = True
@ -139,7 +139,7 @@ class BaseAuthenticator(object):
self.vendor, self.vendor,
e, e,
) )
raise MoulinetteError("unable_authenticate") raise MoulinetteAuthenticationError("unable_authenticate")
else: else:
self.is_authenticated = True self.is_authenticated = True
@ -147,7 +147,7 @@ class BaseAuthenticator(object):
# No credentials given, can't authenticate # No credentials given, can't authenticate
# #
else: else:
raise MoulinetteError("unable_authenticate") raise MoulinetteAuthenticationError("unable_authenticate")
return self return self
@ -175,7 +175,7 @@ class BaseAuthenticator(object):
def _authenticate_session(self, session_id, session_token): def _authenticate_session(self, session_id, session_token):
"""Checks session and token against the stored session token""" """Checks session and token against the stored session token"""
if not self._session_exists(session_id): if not self._session_exists(session_id):
raise MoulinetteError("session_expired") raise MoulinetteAuthenticationError("session_expired")
try: try:
# FIXME : shouldn't we also add a check that this session file # FIXME : shouldn't we also add a check that this session file
# is not too old ? e.g. not older than 24 hours ? idk... # is not too old ? e.g. not older than 24 hours ? idk...
@ -184,7 +184,7 @@ class BaseAuthenticator(object):
stored_hash = f.read() stored_hash = f.read()
except IOError as e: except IOError as e:
logger.debug("unable to retrieve session", exc_info=1) logger.debug("unable to retrieve session", exc_info=1)
raise MoulinetteError("unable_retrieve_session", exception=e) raise MoulinetteAuthenticationError("unable_retrieve_session", exception=e)
else: else:
# #
# session_id (or just id) : This is unique id for the current session from the user. Not too important # session_id (or just id) : This is unique id for the current session from the user. Not too important
@ -206,7 +206,7 @@ class BaseAuthenticator(object):
hash_ = hashlib.sha256(to_hash).hexdigest() hash_ = hashlib.sha256(to_hash).hexdigest()
if not hmac.compare_digest(hash_, stored_hash): if not hmac.compare_digest(hash_, stored_hash):
raise MoulinetteError("invalid_token") raise MoulinetteAuthenticationError("invalid_token")
else: else:
return return

View file

@ -10,7 +10,7 @@ import time
import ldap.modlist as modlist import ldap.modlist as modlist
from moulinette import m18n from moulinette import m18n
from moulinette.core import MoulinetteError, MoulinetteLdapIsDownError from moulinette.core import MoulinetteError, MoulinetteAuthenticationError, MoulinetteLdapIsDownError
from moulinette.authenticators import BaseAuthenticator from moulinette.authenticators import BaseAuthenticator
logger = logging.getLogger("moulinette.authenticator.ldap") logger = logging.getLogger("moulinette.authenticator.ldap")
@ -86,7 +86,7 @@ class Authenticator(BaseAuthenticator):
try: try:
con = _reconnect() con = _reconnect()
except ldap.INVALID_CREDENTIALS: except ldap.INVALID_CREDENTIALS:
raise MoulinetteError("invalid_password") raise MoulinetteAuthenticationError("invalid_password")
except ldap.SERVER_DOWN: except ldap.SERVER_DOWN:
# ldap is down, attempt to restart it before really failing # ldap is down, attempt to restart it before really failing
logger.warning(m18n.g("ldap_server_is_down_restart_it")) logger.warning(m18n.g("ldap_server_is_down_restart_it"))

View file

@ -382,6 +382,8 @@ class MoulinetteSignals(object):
class MoulinetteError(Exception): class MoulinetteError(Exception):
http_code = 500
"""Moulinette base exception""" """Moulinette base exception"""
def __init__(self, key, raw_msg=False, *args, **kwargs): def __init__(self, key, raw_msg=False, *args, **kwargs):
@ -396,6 +398,16 @@ class MoulinetteError(Exception):
return self.strerror return self.strerror
class MoulinetteValidationError(MoulinetteError):
http_code = 400
class MoulinetteAuthenticationError(MoulinetteError):
http_code = 401
class MoulinetteLdapIsDownError(MoulinetteError): class MoulinetteLdapIsDownError(MoulinetteError):
"""Used when ldap is down""" """Used when ldap is down"""

View file

@ -15,7 +15,7 @@ from bottle import abort
from moulinette import msignals, m18n, env from moulinette import msignals, m18n, env
from moulinette.actionsmap import ActionsMap from moulinette.actionsmap import ActionsMap
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError, MoulinetteValidationError
from moulinette.interfaces import ( from moulinette.interfaces import (
BaseActionsMapParser, BaseActionsMapParser,
BaseInterface, BaseInterface,
@ -546,17 +546,17 @@ class _ActionsMapPlugin(object):
# HTTP Responses ------------------------------------------------------- # HTTP Responses -------------------------------------------------------
def moulinette_error_to_http_response(self): def moulinette_error_to_http_response(error):
content = error.content() content = error.content()
if isinstance(content, dict): if isinstance(content, dict):
return HTTPResponse( return HTTPResponse(
json_encode(content), json_encode(content),
400, error.http_code,
headers={"Content-type": "application/json"}, headers={"Content-type": "application/json"},
) )
else: else:
return HTTPResponse(content, 400) return HTTPResponse(content, error.http_code)
def format_for_response(content): def format_for_response(content):
@ -673,7 +673,7 @@ class ActionsMapParser(BaseActionsMapParser):
e, e,
) )
logger.error(error_message) logger.error(error_message)
raise MoulinetteError(error_message, raw_msg=True) raise MoulinetteValidationError(error_message, raw_msg=True)
if self.get_conf(tid, "authenticate"): if self.get_conf(tid, "authenticate"):
authenticator = self.get_conf(tid, "authenticator") authenticator = self.get_conf(tid, "authenticator")
@ -702,7 +702,7 @@ class ActionsMapParser(BaseActionsMapParser):
except KeyError as e: except KeyError as e:
error_message = "no argument parser found for route '%s': %s" % (route, e) error_message = "no argument parser found for route '%s': %s" % (route, e)
logger.error(error_message) logger.error(error_message)
raise MoulinetteError(error_message, raw_msg=True) raise MoulinetteValidationError(error_message, raw_msg=True)
ret = argparse.Namespace() ret = argparse.Namespace()
# TODO: Catch errors? # TODO: Catch errors?

View file

@ -13,7 +13,7 @@ import argcomplete
from moulinette import msignals, m18n from moulinette import msignals, m18n
from moulinette.actionsmap import ActionsMap from moulinette.actionsmap import ActionsMap
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError, MoulinetteValidationError
from moulinette.interfaces import ( from moulinette.interfaces import (
BaseActionsMapParser, BaseActionsMapParser,
BaseInterface, BaseInterface,
@ -411,7 +411,7 @@ class ActionsMapParser(BaseActionsMapParser):
e, e,
) )
logger.exception(error_message) logger.exception(error_message)
raise MoulinetteError(error_message, raw_msg=True) raise MoulinetteValidationError(error_message, raw_msg=True)
tid = getattr(ret, "_tid", None) tid = getattr(ret, "_tid", None)
if self.get_conf(tid, "authenticate"): if self.get_conf(tid, "authenticate"):
@ -439,7 +439,7 @@ class ActionsMapParser(BaseActionsMapParser):
e, e,
) )
logger.exception(error_message) logger.exception(error_message)
raise MoulinetteError(error_message, raw_msg=True) raise MoulinetteValidationError(error_message, raw_msg=True)
else: else:
self.prepare_action_namespace(getattr(ret, "_tid", None), ret) self.prepare_action_namespace(getattr(ret, "_tid", None), ret)
self._parser.dequeue_callbacks(ret) self._parser.dequeue_callbacks(ret)
@ -490,7 +490,7 @@ class Interface(BaseInterface):
""" """
if output_as and output_as not in ["json", "plain", "none"]: if output_as and output_as not in ["json", "plain", "none"]:
raise MoulinetteError("invalid_usage") raise MoulinetteValidationError("invalid_usage")
# auto-complete # auto-complete
argcomplete.autocomplete(self.actionsmap.parser._parser) argcomplete.autocomplete(self.actionsmap.parser._parser)
@ -555,7 +555,7 @@ class Interface(BaseInterface):
if confirm: if confirm:
m = message[0].lower() + message[1:] m = message[0].lower() + message[1:]
if prompt(m18n.g("confirm", prompt=m)) != value: if prompt(m18n.g("confirm", prompt=m)) != value:
raise MoulinetteError("values_mismatch") raise MoulinetteValidationError("values_mismatch")
return value return value