From d015732fa4963f4885a7ef750cc56315beec3083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 13 Nov 2018 19:31:40 +0100 Subject: [PATCH 01/49] Add possiblity to get attribute name of conflict in LDAP --- moulinette/authenticators/ldap.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index cf04f0c1..f36bac7e 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -233,14 +233,32 @@ class Authenticator(BaseAuthenticator): Returns: Boolean | MoulinetteError + """ + attr_found = self.get_conflict(value_dict) + if attr_found: + logger.info("attribute '%s' with value '%s' is not unique", + attr_found[0], attr_found[1]) + raise MoulinetteError(errno.EEXIST, + m18n.g('ldap_attribute_already_exists', + attribute=attr_found[0], value=attr_found[1])) + return True + + def get_conflict(self, value_dict, base_dn=None): + """ + Check uniqueness of values + + Keyword arguments: + value_dict -- Dictionnary of attributes/values to check + + Returns: + None | list with Fist conflict attribute name and value + """ for attr, value in value_dict.items(): - if not self.search(filter=attr + '=' + value): + if not self.search(base=base_dn, filter=attr + '=' + value): continue else: + return (attr, value) logger.info("attribute '%s' with value '%s' is not unique", attr, value) - raise MoulinetteError(errno.EEXIST, - m18n.g('ldap_attribute_already_exists', - attribute=attr, value=value)) - return True + return None From b64e2d768c6e1a7be76dce5de633c1d236ad70e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 26 Nov 2018 09:36:12 +0100 Subject: [PATCH 02/49] Fix identation --- moulinette/authenticators/ldap.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index f36bac7e..6c5aebb0 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -239,8 +239,9 @@ class Authenticator(BaseAuthenticator): logger.info("attribute '%s' with value '%s' is not unique", attr_found[0], attr_found[1]) raise MoulinetteError(errno.EEXIST, - m18n.g('ldap_attribute_already_exists', - attribute=attr_found[0], value=attr_found[1])) + m18n.g('ldap_attribute_already_exists', + attribute=attr_found[0], + value=attr_found[1])) return True def get_conflict(self, value_dict, base_dn=None): From f14bb8a9282576ffcc3b6ed758b16ad92a4ed22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 29 Nov 2018 17:03:21 +0100 Subject: [PATCH 03/49] Fix typo --- moulinette/authenticators/ldap.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 6c5aebb0..8484e491 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -260,6 +260,4 @@ class Authenticator(BaseAuthenticator): continue else: return (attr, value) - logger.info("attribute '%s' with value '%s' is not unique", - attr, value) return None From 84c9a74d3380f59cdc9fda6aa5bf5fac9d619a0c Mon Sep 17 00:00:00 2001 From: Gabriel Corona Date: Sun, 2 Dec 2018 02:32:59 +0100 Subject: [PATCH 04/49] Protect against CSRF (#171) --- moulinette/interfaces/api.py | 31 +++++++++++ setup.py | 3 +- tests/test_api.py | 100 +++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 tests/test_api.py diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 4ce66294..abe3c90b 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -11,6 +11,7 @@ from gevent.queue import Queue from geventwebsocket import WebSocketError from bottle import run, request, response, Bottle, HTTPResponse +from bottle import get, post, install, abort, delete, put from moulinette import msignals, m18n, DATA_DIR from moulinette.core import MoulinetteError, clean_session @@ -26,6 +27,35 @@ logger = log.getLogger('moulinette.interface.api') # API helpers ---------------------------------------------------------- +CSRF_TYPES = set(["text/plain", + "application/x-www-form-urlencoded", + "multipart/form-data"]) + + +def is_csrf(): + """Checks is this is a CSRF request.""" + + if request.method != "POST": + return False + if request.content_type is None: + return True + content_type = request.content_type.lower().split(';')[0] + if content_type not in CSRF_TYPES: + return False + + return request.headers.get("X-Requested-With") is None + + +# Protection against CSRF +def filter_csrf(callback): + def wrapper(*args, **kwargs): + if is_csrf(): + abort(403, "CSRF protection") + else: + return callback(*args, **kwargs) + return wrapper + + class LogQueues(dict): """Map of session id to queue.""" pass @@ -722,6 +752,7 @@ class Interface(BaseInterface): return callback # Install plugins + app.install(filter_csrf) app.install(apiheader) app.install(api18n) app.install(_ActionsMapPlugin(actionsmap, use_websocket, log_queues)) diff --git a/setup.py b/setup.py index b9dddbaa..ea4ded50 100755 --- a/setup.py +++ b/setup.py @@ -30,5 +30,6 @@ setup(name='Moulinette', 'moulinette.interfaces', 'moulinette.utils', ], - data_files=[(LOCALES_DIR, locale_files)] + data_files=[(LOCALES_DIR, locale_files)], + tests_require=["pytest", "webtest"], ) diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 00000000..955fa577 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +from webtest import TestApp as WebTestApp +from bottle import Bottle +from moulinette.interfaces.api import filter_csrf + + +URLENCODED = 'application/x-www-form-urlencoded' +FORMDATA = 'multipart/form-data' +TEXT = 'text/plain' + +TYPES = [URLENCODED, FORMDATA, TEXT] +SAFE_METHODS = ["HEAD", "GET", "PUT", "DELETE"] + + +app = Bottle(autojson=True) +app.install(filter_csrf) + + +@app.get('/') +def get_hello(): + return "Hello World!\n" + + +@app.post('/') +def post_hello(): + return "OK\n" + + +@app.put('/') +def put_hello(): + return "OK\n" + + +@app.delete('/') +def delete_hello(): + return "OK\n" + + +webtest = WebTestApp(app) + + +def test_get(): + r = webtest.get("/") + assert r.status_code == 200 + + +def test_csrf_post(): + r = webtest.post("/", "test", expect_errors=True) + assert r.status_code == 403 + + +def test_post_json(): + r = webtest.post("/", "test", + headers=[("Content-Type", "application/json")]) + assert r.status_code == 200 + + +def test_csrf_post_text(): + r = webtest.post("/", "test", + headers=[("Content-Type", "text/plain")], + expect_errors=True) + assert r.status_code == 403 + + +def test_csrf_post_urlencoded(): + r = webtest.post("/", "test", + headers=[("Content-Type", + "application/x-www-form-urlencoded")], + expect_errors=True) + assert r.status_code == 403 + + +def test_csrf_post_form(): + r = webtest.post("/", "test", + headers=[("Content-Type", "multipart/form-data")], + expect_errors=True) + assert r.status_code == 403 + + +def test_ok_post_text(): + r = webtest.post("/", "test", + headers=[("Content-Type", "text/plain"), + ("X-Requested-With", "XMLHttpRequest")]) + assert r.status_code == 200 + + +def test_ok_post_urlencoded(): + r = webtest.post("/", "test", + headers=[("Content-Type", + "application/x-www-form-urlencoded"), + ("X-Requested-With", "XMLHttpRequest")]) + assert r.status_code == 200 + + +def test_ok_post_form(): + r = webtest.post("/", "test", + headers=[("Content-Type", "multipart/form-data"), + ("X-Requested-With", "XMLHttpRequest")]) + assert r.status_code == 200 From 5125d948af5984c60545f5dfc0244d8acfd47189 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 9 Dec 2018 19:30:54 +0100 Subject: [PATCH 05/49] [enh] Display date as system timezone --- moulinette/interfaces/cli.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 00737b5e..fe382216 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -8,6 +8,9 @@ import locale import logging from argparse import SUPPRESS from collections import OrderedDict +import time +import pytz +from datetime import date, datetime import argcomplete @@ -94,6 +97,24 @@ def plain_print_dict(d, depth=0): print(d) +def pretty_date(_date): + """Display a date in the current time zone without ms and tzinfo + + Argument: + - date -- The date or datetime to display + """ + if time.daylight: + offsetHour = time.altzone / 3600 + else: + offsetHour = time.timezone / 3600 + localtz = 'Etc/GMT%+d' % offsetHour + _date = _date.astimezone(pytz.timezone(localtz)) + if isinstance(_date, datetime): + return _date.strftime("%Y-%m-%d %H:%M:%S") + else: + return _date.strftime("%Y-%m-%d") + + def pretty_print_dict(d, depth=0): """Print in a pretty way a dictionary recursively @@ -127,10 +148,14 @@ def pretty_print_dict(d, depth=0): else: if isinstance(value, unicode): value = value.encode('utf-8') + elif isinstance(v, date): + v = pretty_date(v) print("{:s}- {}".format(" " * (depth + 1), value)) else: if isinstance(v, unicode): v = v.encode('utf-8') + elif isinstance(v, date): + v = pretty_date(v) print("{:s}{}: {}".format(" " * depth, k, v)) From 57602c5a1c18ae61724cc5edc9fa64a7d8d51e06 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 10 Dec 2018 00:00:49 +0100 Subject: [PATCH 06/49] [enh] Transform naive date into UTC aware date --- debian/control | 3 ++- moulinette/interfaces/cli.py | 2 ++ moulinette/utils/serialize.py | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 71191b69..12a34020 100644 --- a/debian/control +++ b/debian/control @@ -16,7 +16,8 @@ Depends: ${misc:Depends}, ${python:Depends}, python-gnupg, python-gevent-websocket, python-argcomplete, - python-psutil + python-psutil, + python-tz Replaces: yunohost-cli Breaks: yunohost-cli Description: prototype interfaces with ease in Python diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index fe382216..edff5346 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -103,6 +103,8 @@ def pretty_date(_date): Argument: - date -- The date or datetime to display """ + if _date.tzinfo is None: + _date = _date.replace(tzinfo=pytz.utc) if time.daylight: offsetHour = time.altzone / 3600 else: diff --git a/moulinette/utils/serialize.py b/moulinette/utils/serialize.py index 800cf1b0..0fbea631 100644 --- a/moulinette/utils/serialize.py +++ b/moulinette/utils/serialize.py @@ -1,6 +1,7 @@ import logging from json.encoder import JSONEncoder import datetime +import pytz logger = logging.getLogger('moulinette.utils.serialize') @@ -26,7 +27,9 @@ class JSONExtendedEncoder(JSONEncoder): return list(o) # Display the date in its iso format ISO-8601 Internet Profile (RFC 3339) - if isinstance(o, datetime.datetime) or isinstance(o, datetime.date): + if isinstance(o, datetime.date): + if o.tzinfo is None: + o = o.replace(tzinfo=pytz.utc) return o.isoformat() # Return the repr for object that json can't encode From 8b8296e87b09026c849349f8073a04a2873c16b1 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 10 Dec 2018 01:17:04 +0100 Subject: [PATCH 07/49] [fix] Better way to calculate locale timezone --- moulinette/interfaces/cli.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index edff5346..ce5efae0 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -103,13 +103,19 @@ def pretty_date(_date): Argument: - date -- The date or datetime to display """ + # Deduce system timezone + nowutc = datetime.now(tz=pytz.utc) + nowtz = datetime.now() + nowtz = nowtz.replace(tzinfo=pytz.utc) + offsetHour = nowutc - nowtz + offsetHour = int(round(offsetHour.total_seconds() / 3600)) + localtz = 'Etc/GMT%+d' % offsetHour + + # Transform naive date into UTC date if _date.tzinfo is None: _date = _date.replace(tzinfo=pytz.utc) - if time.daylight: - offsetHour = time.altzone / 3600 - else: - offsetHour = time.timezone / 3600 - localtz = 'Etc/GMT%+d' % offsetHour + + # Convert UTC date into system locale date _date = _date.astimezone(pytz.timezone(localtz)) if isinstance(_date, datetime): return _date.strftime("%Y-%m-%d %H:%M:%S") From dcf4c8fcf0508b4033e1b79c4b5d591a7a5e9c72 Mon Sep 17 00:00:00 2001 From: Irina LAMBLA Date: Sun, 25 Nov 2018 15:07:39 +0100 Subject: [PATCH 08/49] Enh Simplify moulinette error --- moulinette/actionsmap.py | 10 ++--- moulinette/authenticators/__init__.py | 10 ++--- moulinette/authenticators/ldap.py | 9 ++--- moulinette/core.py | 11 +++--- moulinette/interfaces/__init__.py | 17 ++++----- moulinette/interfaces/api.py | 9 ++--- moulinette/interfaces/cli.py | 8 ++-- moulinette/utils/filesystem.py | 53 +++++++-------------------- moulinette/utils/network.py | 18 +++------ 9 files changed, 53 insertions(+), 92 deletions(-) diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 4921fb9c..01c63224 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -187,8 +187,7 @@ class PatternParameter(_ExtraParameter): if msg == message: msg = m18n.g(message) - raise MoulinetteError(errno.EINVAL, - m18n.g('invalid_argument', + raise MoulinetteError(m18n.g('invalid_argument', argument=arg_name, error=msg)) return arg_value @@ -218,8 +217,7 @@ class RequiredParameter(_ExtraParameter): if required and (arg_value is None or arg_value == ''): logger.debug("argument '%s' is required", arg_name) - raise MoulinetteError(errno.EINVAL, - m18n.g('argument_required', + raise MoulinetteError(m18n.g('argument_required', argument=arg_name)) return arg_value @@ -285,7 +283,7 @@ class ExtraArgumentParser(object): except Exception as e: logger.error("unable to validate extra parameter '%s' " "for argument '%s': %s", p, arg_name, e) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) return parameters @@ -501,7 +499,7 @@ class ActionsMap(object): except (AttributeError, ImportError): logger.exception("unable to load function %s.%s", namespace, func_name) - raise MoulinetteError(errno.EIO, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) else: log_id = start_action_logging() if logger.isEnabledFor(logging.DEBUG): diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index 578ef490..24a3d9ab 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -97,7 +97,7 @@ class BaseAuthenticator(object): except TypeError: logger.error("unable to extract token parts from '%s'", token) if password is None: - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) logger.info("session will not be stored") store_session = False @@ -114,7 +114,7 @@ class BaseAuthenticator(object): except: logger.exception("authentication (name: '%s', vendor: '%s') fails", self.name, self.vendor) - raise MoulinetteError(errno.EACCES, m18n.g('unable_authenticate')) + raise MoulinetteError(m18n.g('unable_authenticate')) # Store session if store_session: @@ -149,8 +149,7 @@ class BaseAuthenticator(object): enc_pwd = f.read() except IOError: logger.debug("unable to retrieve session", exc_info=1) - raise MoulinetteError(errno.ENOENT, - m18n.g('unable_retrieve_session')) + raise MoulinetteError(m18n.g('unable_retrieve_session')) else: gpg = gnupg.GPG() gpg.encoding = 'utf-8' @@ -159,6 +158,5 @@ class BaseAuthenticator(object): if decrypted.ok is not True: logger.error("unable to decrypt password for the session: %s", decrypted.status) - raise MoulinetteError(errno.EINVAL, - m18n.g('unable_retrieve_session')) + raise MoulinetteError(m18n.g('unable_retrieve_session')) return decrypted.data diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 8484e491..f9d50c28 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -82,7 +82,7 @@ class Authenticator(BaseAuthenticator): else: con.simple_bind_s() except ldap.INVALID_CREDENTIALS: - raise MoulinetteError(errno.EACCES, m18n.g('invalid_password')) + raise MoulinetteError(m18n.g('invalid_password')) except ldap.SERVER_DOWN: logger.exception('unable to reach the server to authenticate') raise MoulinetteError(169, m18n.g('ldap_server_down')) @@ -238,10 +238,9 @@ class Authenticator(BaseAuthenticator): if attr_found: logger.info("attribute '%s' with value '%s' is not unique", attr_found[0], attr_found[1]) - raise MoulinetteError(errno.EEXIST, - m18n.g('ldap_attribute_already_exists', - attribute=attr_found[0], - value=attr_found[1])) + raise MoulinetteError('ldap_attribute_already_exists', + attribute=attr_found[0], + value=attr_found[1]) return True def get_conflict(self, value_dict, base_dn=None): diff --git a/moulinette/core.py b/moulinette/core.py index c6e367f7..2b0b20be 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -344,7 +344,7 @@ def init_interface(name, kwargs={}, actionsmap={}): mod = import_module('moulinette.interfaces.%s' % name) except ImportError: logger.exception("unable to load interface '%s'", name) - raise MoulinetteError(errno.EINVAL, moulinette.m18n.g('error_see_log')) + raise MoulinetteError(moulinette.m18n.g('error_see_log')) else: try: # Retrieve interface classes @@ -352,7 +352,7 @@ def init_interface(name, kwargs={}, actionsmap={}): interface = mod.Interface except AttributeError: logger.exception("unable to retrieve classes of interface '%s'", name) - raise MoulinetteError(errno.EIO, moulinette.m18n.g('error_see_log')) + raise MoulinetteError(moulinette.m18n.g('error_see_log')) # Instantiate or retrieve ActionsMap if isinstance(actionsmap, dict): @@ -361,7 +361,7 @@ def init_interface(name, kwargs={}, actionsmap={}): amap = actionsmap else: logger.error("invalid actionsmap value %r", actionsmap) - raise MoulinetteError(errno.EINVAL, moulinette.m18n.g('error_see_log')) + raise MoulinetteError(moulinette.m18n.g('error_see_log')) return interface(amap, **kwargs) @@ -382,7 +382,7 @@ def init_authenticator((vendor, name), kwargs={}): mod = import_module('moulinette.authenticators.%s' % vendor) except ImportError: logger.exception("unable to load authenticator vendor '%s'", vendor) - raise MoulinetteError(errno.EINVAL, moulinette.m18n.g('error_see_log')) + raise MoulinetteError(moulinette.m18n.g('error_see_log')) else: return mod.Authenticator(name, **kwargs) @@ -471,8 +471,7 @@ class MoulinetteLock(object): break if self.timeout is not None and (time.time() - start_time) > self.timeout: - raise MoulinetteError(errno.EBUSY, - moulinette.m18n.g('instance_already_running')) + raise MoulinetteError(moulinette.m18n.g('instance_already_running')) # Wait before checking again time.sleep(self.interval) diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index 678c9526..811d6cfa 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -142,7 +142,7 @@ class BaseActionsMapParser(object): # Validate tid and namespace if not isinstance(tid, tuple) and \ (namespace is None or not hasattr(namespace, TO_RETURN_PROP)): - raise MoulinetteError(errno.EINVAL, m18n.g('invalid_usage')) + raise MoulinetteError(m18n.g('invalid_usage')) elif not tid: tid = GLOBAL_SECTION @@ -158,8 +158,7 @@ class BaseActionsMapParser(object): # TODO: Catch errors auth = msignals.authenticate(cls(), **auth_conf) if not auth.is_authenticated: - raise MoulinetteError(errno.EACCES, - m18n.g('authentication_required_long')) + raise MoulinetteError(m18n.g('authentication_required_long')) if self.get_conf(tid, 'argument_auth') and \ self.get_conf(tid, 'authenticate') == 'all': namespace.auth = auth @@ -263,7 +262,7 @@ class BaseActionsMapParser(object): else: logger.error("expecting 'all', 'False' or a list for " "configuration 'authenticate', got %r", ifaces) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) # -- 'authenticator' try: @@ -278,7 +277,7 @@ class BaseActionsMapParser(object): except KeyError: logger.error("requesting profile '%s' which is undefined in " "global configuration of 'authenticator'", auth) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) elif is_global and isinstance(auth, dict): if len(auth) == 0: logger.warning('no profile defined in global configuration ' @@ -301,7 +300,7 @@ class BaseActionsMapParser(object): else: logger.error("expecting a dict of profile(s) or a profile name " "for configuration 'authenticator', got %r", auth) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) # -- 'argument_auth' try: @@ -314,7 +313,7 @@ class BaseActionsMapParser(object): else: logger.error("expecting a boolean for configuration " "'argument_auth', got %r", arg_auth) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) # -- 'lock' try: @@ -327,7 +326,7 @@ class BaseActionsMapParser(object): else: logger.error("expecting a boolean for configuration 'lock', " "got %r", lock) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) return conf @@ -427,7 +426,7 @@ class _CallbackAction(argparse.Action): except: logger.exception("cannot get value from callback method " "'{0}'".format(self.callback_method)) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) else: if value: if self.callback_return: diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index abe3c90b..0bb42c2c 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -660,7 +660,7 @@ class ActionsMapParser(BaseActionsMapParser): tid, parser = self._parsers[route] except KeyError: logger.error("no argument parser found for route '%s'", route) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) ret = argparse.Namespace() # Perform authentication if needed @@ -673,7 +673,7 @@ class ActionsMapParser(BaseActionsMapParser): # TODO: Catch errors auth = msignals.authenticate(klass(), **auth_conf) if not auth.is_authenticated: - raise MoulinetteError(errno.EACCES, m18n.g('authentication_required_long')) + raise MoulinetteError(m18n.g('authentication_required_long')) if self.get_conf(tid, 'argument_auth') and \ self.get_conf(tid, 'authenticate') == 'all': ret.auth = auth @@ -796,9 +796,8 @@ class Interface(BaseInterface): logger.exception("unable to start the server instance on %s:%d", host, port) if e.args[0] == errno.EADDRINUSE: - raise MoulinetteError(errno.EADDRINUSE, - m18n.g('server_already_running')) - raise MoulinetteError(errno.EIO, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('server_already_running')) + raise MoulinetteError(m18n.g('error_see_log')) # Routes handlers diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index ce5efae0..b31f7e84 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -362,7 +362,7 @@ class ActionsMapParser(BaseActionsMapParser): raise except: logger.exception("unable to parse arguments '%s'", ' '.join(args)) - raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) + raise MoulinetteError(m18n.g('error_see_log')) else: self.prepare_action_namespace(getattr(ret, '_tid', None), ret) self._parser.dequeue_callbacks(ret) @@ -409,7 +409,7 @@ class Interface(BaseInterface): """ if output_as and output_as not in ['json', 'plain', 'none']: - raise MoulinetteError(errno.EINVAL, m18n.g('invalid_usage')) + raise MoulinetteError(m18n.g('invalid_usage')) # auto-complete argcomplete.autocomplete(self.actionsmap.parser._parser) @@ -422,7 +422,7 @@ class Interface(BaseInterface): try: ret = self.actionsmap.process(args, timeout=timeout) except (KeyboardInterrupt, EOFError): - raise MoulinetteError(errno.EINTR, m18n.g('operation_interrupted')) + raise MoulinetteError(m18n.g('operation_interrupted')) if ret is None or output_as == 'none': return @@ -472,7 +472,7 @@ class Interface(BaseInterface): if confirm: m = message[0].lower() + message[1:] if prompt(m18n.g('confirm', prompt=m)) != value: - raise MoulinetteError(errno.EINVAL, m18n.g('values_mismatch')) + raise MoulinetteError(m18n.g('values_mismatch')) return value diff --git a/moulinette/utils/filesystem.py b/moulinette/utils/filesystem.py index 8d229e29..97a48d63 100644 --- a/moulinette/utils/filesystem.py +++ b/moulinette/utils/filesystem.py @@ -23,21 +23,16 @@ def read_file(file_path): # Check file exists if not os.path.isfile(file_path): - raise MoulinetteError(errno.ENOENT, - m18n.g('file_not_exist', path=file_path)) + raise MoulinetteError(m18n.g('file_not_exist', path=file_path)) # Open file and read content try: with open(file_path, "r") as f: file_content = f.read() except IOError as e: - raise MoulinetteError(errno.EACCES, - m18n.g('cannot_open_file', - file=file_path, error=str(e))) + raise MoulinetteError(m18n.g('cannot_open_file', file=file_path, error=str(e))) except Exception as e: - raise MoulinetteError(errno.EIO, - m18n.g('error_reading_file', - file=file_path, error=str(e))) + raise MoulinetteError(m18n.g('error_reading_file', file=file_path, error=str(e))) return file_content @@ -57,9 +52,7 @@ def read_json(file_path): try: loaded_json = json.loads(file_content) except ValueError as e: - raise MoulinetteError(errno.EINVAL, - m18n.g('corrupted_json', - ressource=file_path, error=str(e))) + raise MoulinetteError(m18n.g('corrupted_json', ressource=file_path, error=str(e))) return loaded_json @@ -79,9 +72,7 @@ def read_yaml(file_path): try: loaded_yaml = yaml.safe_load(file_content) except ValueError as e: - raise MoulinetteError(errno.EINVAL, - m18n.g('corrupted_yaml', - ressource=file_path, error=str(e))) + raise MoulinetteError(m18n.g('corrupted_yaml', ressource=file_path, error=str(e))) return loaded_yaml @@ -111,13 +102,9 @@ def write_to_file(file_path, data, file_mode="w"): with open(file_path, file_mode) as f: f.write(data) except IOError as e: - raise MoulinetteError(errno.EACCES, - m18n.g('cannot_write_file', - file=file_path, error=str(e))) + raise MoulinetteError(m18n.g('cannot_write_file', file=file_path, error=str(e))) except Exception as e: - raise MoulinetteError(errno.EIO, - m18n.g('error_writing_file', - file=file_path, error=str(e))) + raise MoulinetteError(m18n.g('error_writing_file', file=file_path, error=str(e))) def append_to_file(file_path, data): @@ -152,13 +139,9 @@ def write_to_json(file_path, data): with open(file_path, "w") as f: json.dump(data, f) except IOError as e: - raise MoulinetteError(errno.EACCES, - m18n.g('cannot_write_file', - file=file_path, error=str(e))) + raise MoulinetteError(m18n.g('cannot_write_file', file=file_path, error=str(e))) except Exception as e: - raise MoulinetteError(errno.EIO, - m18n.g('_error_writing_file', - file=file_path, error=str(e))) + raise MoulinetteError(m18n.g('_error_writing_file', file=file_path, error=str(e))) def mkdir(path, mode=0777, parents=False, uid=None, gid=None, force=False): @@ -223,16 +206,14 @@ def chown(path, uid=None, gid=None, recursive=False): try: uid = getpwnam(uid).pw_uid except KeyError: - raise MoulinetteError(errno.EINVAL, - m18n.g('unknown_user', user=uid)) + raise MoulinetteError(m18n.g('unknown_user', user=uid)) elif uid is None: uid = -1 if isinstance(gid, basestring): try: gid = grp.getgrnam(gid).gr_gid except KeyError: - raise MoulinetteError(errno.EINVAL, - m18n.g('unknown_group', group=gid)) + raise MoulinetteError(m18n.g('unknown_group', group=gid)) elif gid is None: gid = -1 @@ -245,9 +226,7 @@ def chown(path, uid=None, gid=None, recursive=False): for f in files: os.chown(os.path.join(root, f), uid, gid) except Exception as e: - raise MoulinetteError(errno.EIO, - m18n.g('error_changing_file_permissions', - path=path, error=str(e))) + raise MoulinetteError(m18n.g('error_changing_file_permissions', path=path, error=str(e))) def chmod(path, mode, fmode=None, recursive=False): @@ -271,9 +250,7 @@ def chmod(path, mode, fmode=None, recursive=False): for f in files: os.chmod(os.path.join(root, f), fmode) except Exception as e: - raise MoulinetteError(errno.EIO, - m18n.g('error_changing_file_permissions', - path=path, error=str(e))) + raise MoulinetteError(m18n.g('error_changing_file_permissions', path=path, error=str(e))) def rm(path, recursive=False, force=False): @@ -292,6 +269,4 @@ def rm(path, recursive=False, force=False): os.remove(path) except OSError as e: if not force: - raise MoulinetteError(errno.EIO, - m18n.g('error_removing', - path=path, error=str(e))) + raise MoulinetteError(m18n.g('error_removing', path=path, error=str(e))) diff --git a/moulinette/utils/network.py b/moulinette/utils/network.py index 27e753e3..fb87816e 100644 --- a/moulinette/utils/network.py +++ b/moulinette/utils/network.py @@ -25,26 +25,21 @@ def download_text(url, timeout=30, expected_status_code=200): r = requests.get(url, timeout=timeout) # Invalid URL except requests.exceptions.ConnectionError: - raise MoulinetteError(errno.EBADE, - m18n.g('invalid_url', url=url)) + raise MoulinetteError(m18n.g('invalid_url', url=url)) # SSL exceptions except requests.exceptions.SSLError: - raise MoulinetteError(errno.EBADE, - m18n.g('download_ssl_error', url=url)) + raise MoulinetteError(m18n.g('download_ssl_error', url=url)) # Timeout exceptions except requests.exceptions.Timeout: - raise MoulinetteError(errno.ETIME, - m18n.g('download_timeout', url=url)) + raise MoulinetteError(m18n.g('download_timeout', url=url)) # Unknown stuff except Exception as e: - raise MoulinetteError(errno.ECONNRESET, - m18n.g('download_unknown_error', + raise MoulinetteError(m18n.g('download_unknown_error', url=url, error=str(e))) # Assume error if status code is not 200 (OK) if expected_status_code is not None \ and r.status_code != expected_status_code: - raise MoulinetteError(errno.EBADE, - m18n.g('download_bad_status_code', + raise MoulinetteError(m18n.g('download_bad_status_code', url=url, code=str(r.status_code))) return r.text @@ -66,7 +61,6 @@ def download_json(url, timeout=30, expected_status_code=200): try: loaded_json = json.loads(text) except ValueError: - raise MoulinetteError(errno.EINVAL, - m18n.g('corrupted_json', ressource=url)) + raise MoulinetteError(m18n.g('corrupted_json', ressource=url)) return loaded_json From 6614d8c76b85adf9f56aa2701b0390707f29b642 Mon Sep 17 00:00:00 2001 From: Irina LAMBLA Date: Sat, 1 Dec 2018 00:08:56 +0100 Subject: [PATCH 09/49] use StandardError --- moulinette/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/moulinette/core.py b/moulinette/core.py index 2b0b20be..ce862e6e 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -411,9 +411,11 @@ def clean_session(session_id, profiles=[]): # Moulinette core classes ---------------------------------------------- -class MoulinetteError(OSError): +class MoulinetteError(StandardError): """Moulinette base exception""" - pass + def __init__(self, key, *args, **kwargs): + msg = m18n.g(key, *args, **kwargs) + super(MoulinetteError, self).__init__(msg) class MoulinetteLock(object): From a6cce50977159291c97bd9703c450ac30921f448 Mon Sep 17 00:00:00 2001 From: Irina LAMBLA Date: Sat, 8 Dec 2018 00:08:07 +0100 Subject: [PATCH 10/49] corrections --- moulinette/__init__.py | 2 +- moulinette/core.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 1b1207e6..719c5dd3 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -137,5 +137,5 @@ def cli(namespaces, args, use_cache=True, output_as=None, except MoulinetteError as e: import logging logging.getLogger(namespaces[0]).error(e.strerror) - return e.errno + return 1 return 0 diff --git a/moulinette/core.py b/moulinette/core.py index ce862e6e..f56ffd21 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -414,8 +414,9 @@ def clean_session(session_id, profiles=[]): class MoulinetteError(StandardError): """Moulinette base exception""" def __init__(self, key, *args, **kwargs): - msg = m18n.g(key, *args, **kwargs) + msg = moulinette.m18n.g(key, *args, **kwargs) super(MoulinetteError, self).__init__(msg) + self.strerror = msg class MoulinetteLock(object): From a58d4dbb87bee52463fddb49fc9a40ce994aee77 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 12 Dec 2018 02:31:29 +0000 Subject: [PATCH 11/49] Remove old m18n.g and errno --- locales/en.json | 1 - moulinette/actionsmap.py | 13 ++++++------- moulinette/authenticators/__init__.py | 10 ++++------ moulinette/authenticators/ldap.py | 14 ++++++-------- moulinette/core.py | 16 ++++++--------- moulinette/interfaces/__init__.py | 17 ++++++++-------- moulinette/interfaces/api.py | 10 +++++----- moulinette/interfaces/cli.py | 9 ++++----- moulinette/utils/filesystem.py | 28 +++++++++++++-------------- moulinette/utils/network.py | 18 ++++++++--------- 10 files changed, 61 insertions(+), 75 deletions(-) diff --git a/locales/en.json b/locales/en.json index e46a8751..3c872c62 100644 --- a/locales/en.json +++ b/locales/en.json @@ -27,7 +27,6 @@ "operation_interrupted": "Operation interrupted", "password": "Password", "pattern_not_match": "Does not match pattern", - "permission_denied": "Permission denied", "root_required": "You must be root to perform this action", "server_already_running": "A server is already running on that port", "success": "Success!", diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 01c63224..011a156a 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -2,7 +2,6 @@ import os import re -import errno import logging import yaml import cPickle as pickle @@ -187,8 +186,8 @@ class PatternParameter(_ExtraParameter): if msg == message: msg = m18n.g(message) - raise MoulinetteError(m18n.g('invalid_argument', - argument=arg_name, error=msg)) + raise MoulinetteError('invalid_argument', + argument=arg_name, error=msg) return arg_value @staticmethod @@ -217,8 +216,8 @@ class RequiredParameter(_ExtraParameter): if required and (arg_value is None or arg_value == ''): logger.debug("argument '%s' is required", arg_name) - raise MoulinetteError(m18n.g('argument_required', - argument=arg_name)) + raise MoulinetteError('argument_required', + argument=arg_name) return arg_value @staticmethod @@ -283,7 +282,7 @@ class ExtraArgumentParser(object): except Exception as e: logger.error("unable to validate extra parameter '%s' " "for argument '%s': %s", p, arg_name, e) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') return parameters @@ -499,7 +498,7 @@ class ActionsMap(object): except (AttributeError, ImportError): logger.exception("unable to load function %s.%s", namespace, func_name) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') else: log_id = start_action_logging() if logger.isEnabledFor(logging.DEBUG): diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index 24a3d9ab..8357bdb7 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -import errno import gnupg import logging -from moulinette import m18n from moulinette.cache import open_cachefile from moulinette.core import MoulinetteError @@ -97,7 +95,7 @@ class BaseAuthenticator(object): except TypeError: logger.error("unable to extract token parts from '%s'", token) if password is None: - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') logger.info("session will not be stored") store_session = False @@ -114,7 +112,7 @@ class BaseAuthenticator(object): except: logger.exception("authentication (name: '%s', vendor: '%s') fails", self.name, self.vendor) - raise MoulinetteError(m18n.g('unable_authenticate')) + raise MoulinetteError('unable_authenticate') # Store session if store_session: @@ -149,7 +147,7 @@ class BaseAuthenticator(object): enc_pwd = f.read() except IOError: logger.debug("unable to retrieve session", exc_info=1) - raise MoulinetteError(m18n.g('unable_retrieve_session')) + raise MoulinetteError('unable_retrieve_session') else: gpg = gnupg.GPG() gpg.encoding = 'utf-8' @@ -158,5 +156,5 @@ class BaseAuthenticator(object): if decrypted.ok is not True: logger.error("unable to decrypt password for the session: %s", decrypted.status) - raise MoulinetteError(m18n.g('unable_retrieve_session')) + raise MoulinetteError('unable_retrieve_session') return decrypted.data diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index f9d50c28..1b5e034b 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -2,7 +2,6 @@ # TODO: Use Python3 to remove this fix! from __future__ import absolute_import -import errno import logging import random import string @@ -10,7 +9,6 @@ import crypt import ldap import ldap.modlist as modlist -from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.authenticators import BaseAuthenticator @@ -82,10 +80,10 @@ class Authenticator(BaseAuthenticator): else: con.simple_bind_s() except ldap.INVALID_CREDENTIALS: - raise MoulinetteError(m18n.g('invalid_password')) + raise MoulinetteError('invalid_password') except ldap.SERVER_DOWN: logger.exception('unable to reach the server to authenticate') - raise MoulinetteError(169, m18n.g('ldap_server_down')) + raise MoulinetteError('ldap_server_down') else: self.con = con self._ensure_password_uses_strong_hash(password) @@ -137,7 +135,7 @@ class Authenticator(BaseAuthenticator): except Exception as e: logger.exception("error during LDAP search operation with: base='%s', " "filter='%s', attrs=%s and exception %s", base, filter, attrs, e) - raise MoulinetteError(169, m18n.g('ldap_operation_error')) + raise MoulinetteError('ldap_operation_error') result_list = [] if not attrs or 'dn' not in attrs: @@ -168,7 +166,7 @@ class Authenticator(BaseAuthenticator): except Exception as e: logger.exception("error during LDAP add operation with: rdn='%s', " "attr_dict=%s and exception %s", rdn, attr_dict, e) - raise MoulinetteError(169, m18n.g('ldap_operation_error')) + raise MoulinetteError('ldap_operation_error') else: return True @@ -188,7 +186,7 @@ class Authenticator(BaseAuthenticator): self.con.delete_s(dn) except Exception as e: logger.exception("error during LDAP delete operation with: rdn='%s' and exception %s", rdn, e) - raise MoulinetteError(169, m18n.g('ldap_operation_error')) + raise MoulinetteError('ldap_operation_error') else: return True @@ -219,7 +217,7 @@ class Authenticator(BaseAuthenticator): logger.exception("error during LDAP update operation with: rdn='%s', " "attr_dict=%s, new_rdn=%s and exception: %s", rdn, attr_dict, new_rdn, e) - raise MoulinetteError(169, m18n.g('ldap_operation_error')) + raise MoulinetteError('ldap_operation_error') else: return True diff --git a/moulinette/core.py b/moulinette/core.py index f56ffd21..1bdd602a 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -3,7 +3,6 @@ import os import time import json -import errno import logging import psutil @@ -344,7 +343,7 @@ def init_interface(name, kwargs={}, actionsmap={}): mod = import_module('moulinette.interfaces.%s' % name) except ImportError: logger.exception("unable to load interface '%s'", name) - raise MoulinetteError(moulinette.m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') else: try: # Retrieve interface classes @@ -352,7 +351,7 @@ def init_interface(name, kwargs={}, actionsmap={}): interface = mod.Interface except AttributeError: logger.exception("unable to retrieve classes of interface '%s'", name) - raise MoulinetteError(moulinette.m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') # Instantiate or retrieve ActionsMap if isinstance(actionsmap, dict): @@ -361,7 +360,7 @@ def init_interface(name, kwargs={}, actionsmap={}): amap = actionsmap else: logger.error("invalid actionsmap value %r", actionsmap) - raise MoulinetteError(moulinette.m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') return interface(amap, **kwargs) @@ -382,7 +381,7 @@ def init_authenticator((vendor, name), kwargs={}): mod = import_module('moulinette.authenticators.%s' % vendor) except ImportError: logger.exception("unable to load authenticator vendor '%s'", vendor) - raise MoulinetteError(moulinette.m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') else: return mod.Authenticator(name, **kwargs) @@ -474,7 +473,7 @@ class MoulinetteLock(object): break if self.timeout is not None and (time.time() - start_time) > self.timeout: - raise MoulinetteError(moulinette.m18n.g('instance_already_running')) + raise MoulinetteError('instance_already_running') # Wait before checking again time.sleep(self.interval) @@ -497,10 +496,7 @@ class MoulinetteLock(object): with open(self._lockfile, 'w') as f: f.write(str(os.getpid())) except IOError: - raise MoulinetteError( - errno.EPERM, '%s. %s.'.format( - moulinette.m18n.g('permission_denied'), - moulinette.m18n.g('root_required'))) + raise MoulinetteError('root_required') def _lock_PIDs(self): diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index 811d6cfa..fc7185e4 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import re -import errno import logging import argparse import copy @@ -142,7 +141,7 @@ class BaseActionsMapParser(object): # Validate tid and namespace if not isinstance(tid, tuple) and \ (namespace is None or not hasattr(namespace, TO_RETURN_PROP)): - raise MoulinetteError(m18n.g('invalid_usage')) + raise MoulinetteError('invalid_usage') elif not tid: tid = GLOBAL_SECTION @@ -158,7 +157,7 @@ class BaseActionsMapParser(object): # TODO: Catch errors auth = msignals.authenticate(cls(), **auth_conf) if not auth.is_authenticated: - raise MoulinetteError(m18n.g('authentication_required_long')) + raise MoulinetteError('authentication_required_long') if self.get_conf(tid, 'argument_auth') and \ self.get_conf(tid, 'authenticate') == 'all': namespace.auth = auth @@ -262,7 +261,7 @@ class BaseActionsMapParser(object): else: logger.error("expecting 'all', 'False' or a list for " "configuration 'authenticate', got %r", ifaces) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') # -- 'authenticator' try: @@ -277,7 +276,7 @@ class BaseActionsMapParser(object): except KeyError: logger.error("requesting profile '%s' which is undefined in " "global configuration of 'authenticator'", auth) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') elif is_global and isinstance(auth, dict): if len(auth) == 0: logger.warning('no profile defined in global configuration ' @@ -300,7 +299,7 @@ class BaseActionsMapParser(object): else: logger.error("expecting a dict of profile(s) or a profile name " "for configuration 'authenticator', got %r", auth) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') # -- 'argument_auth' try: @@ -313,7 +312,7 @@ class BaseActionsMapParser(object): else: logger.error("expecting a boolean for configuration " "'argument_auth', got %r", arg_auth) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') # -- 'lock' try: @@ -326,7 +325,7 @@ class BaseActionsMapParser(object): else: logger.error("expecting a boolean for configuration 'lock', " "got %r", lock) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') return conf @@ -426,7 +425,7 @@ class _CallbackAction(argparse.Action): except: logger.exception("cannot get value from callback method " "'{0}'".format(self.callback_method)) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') else: if value: if self.callback_return: diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 0bb42c2c..9cef8523 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -189,7 +189,7 @@ class _HTTPArgumentParser(object): def _error(self, message): # TODO: Raise a proper exception - raise MoulinetteError(1, message) + raise MoulinetteError(message) class _ActionsMapPlugin(object): @@ -660,7 +660,7 @@ class ActionsMapParser(BaseActionsMapParser): tid, parser = self._parsers[route] except KeyError: logger.error("no argument parser found for route '%s'", route) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') ret = argparse.Namespace() # Perform authentication if needed @@ -673,7 +673,7 @@ class ActionsMapParser(BaseActionsMapParser): # TODO: Catch errors auth = msignals.authenticate(klass(), **auth_conf) if not auth.is_authenticated: - raise MoulinetteError(m18n.g('authentication_required_long')) + raise MoulinetteError('authentication_required_long') if self.get_conf(tid, 'argument_auth') and \ self.get_conf(tid, 'authenticate') == 'all': ret.auth = auth @@ -796,8 +796,8 @@ class Interface(BaseInterface): logger.exception("unable to start the server instance on %s:%d", host, port) if e.args[0] == errno.EADDRINUSE: - raise MoulinetteError(m18n.g('server_already_running')) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('server_already_running') + raise MoulinetteError('error_see_log') # Routes handlers diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index b31f7e84..c842813a 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -2,7 +2,6 @@ import os import sys -import errno import getpass import locale import logging @@ -362,7 +361,7 @@ class ActionsMapParser(BaseActionsMapParser): raise except: logger.exception("unable to parse arguments '%s'", ' '.join(args)) - raise MoulinetteError(m18n.g('error_see_log')) + raise MoulinetteError('error_see_log') else: self.prepare_action_namespace(getattr(ret, '_tid', None), ret) self._parser.dequeue_callbacks(ret) @@ -409,7 +408,7 @@ class Interface(BaseInterface): """ if output_as and output_as not in ['json', 'plain', 'none']: - raise MoulinetteError(m18n.g('invalid_usage')) + raise MoulinetteError('invalid_usage') # auto-complete argcomplete.autocomplete(self.actionsmap.parser._parser) @@ -422,7 +421,7 @@ class Interface(BaseInterface): try: ret = self.actionsmap.process(args, timeout=timeout) except (KeyboardInterrupt, EOFError): - raise MoulinetteError(m18n.g('operation_interrupted')) + raise MoulinetteError('operation_interrupted') if ret is None or output_as == 'none': return @@ -472,7 +471,7 @@ class Interface(BaseInterface): if confirm: m = message[0].lower() + message[1:] if prompt(m18n.g('confirm', prompt=m)) != value: - raise MoulinetteError(m18n.g('values_mismatch')) + raise MoulinetteError('values_mismatch') return value diff --git a/moulinette/utils/filesystem.py b/moulinette/utils/filesystem.py index 97a48d63..b984c785 100644 --- a/moulinette/utils/filesystem.py +++ b/moulinette/utils/filesystem.py @@ -23,16 +23,16 @@ def read_file(file_path): # Check file exists if not os.path.isfile(file_path): - raise MoulinetteError(m18n.g('file_not_exist', path=file_path)) + raise MoulinetteError('file_not_exist', path=file_path) # Open file and read content try: with open(file_path, "r") as f: file_content = f.read() except IOError as e: - raise MoulinetteError(m18n.g('cannot_open_file', file=file_path, error=str(e))) + raise MoulinetteError('cannot_open_file', file=file_path, error=str(e)) except Exception as e: - raise MoulinetteError(m18n.g('error_reading_file', file=file_path, error=str(e))) + raise MoulinetteError('error_reading_file', file=file_path, error=str(e)) return file_content @@ -52,7 +52,7 @@ def read_json(file_path): try: loaded_json = json.loads(file_content) except ValueError as e: - raise MoulinetteError(m18n.g('corrupted_json', ressource=file_path, error=str(e))) + raise MoulinetteError('corrupted_json', ressource=file_path, error=str(e)) return loaded_json @@ -72,7 +72,7 @@ def read_yaml(file_path): try: loaded_yaml = yaml.safe_load(file_content) except ValueError as e: - raise MoulinetteError(m18n.g('corrupted_yaml', ressource=file_path, error=str(e))) + raise MoulinetteError('corrupted_yaml', ressource=file_path, error=str(e)) return loaded_yaml @@ -102,9 +102,9 @@ def write_to_file(file_path, data, file_mode="w"): with open(file_path, file_mode) as f: f.write(data) except IOError as e: - raise MoulinetteError(m18n.g('cannot_write_file', file=file_path, error=str(e))) + raise MoulinetteError('cannot_write_file', file=file_path, error=str(e)) except Exception as e: - raise MoulinetteError(m18n.g('error_writing_file', file=file_path, error=str(e))) + raise MoulinetteError('error_writing_file', file=file_path, error=str(e)) def append_to_file(file_path, data): @@ -139,9 +139,9 @@ def write_to_json(file_path, data): with open(file_path, "w") as f: json.dump(data, f) except IOError as e: - raise MoulinetteError(m18n.g('cannot_write_file', file=file_path, error=str(e))) + raise MoulinetteError('cannot_write_file', file=file_path, error=str(e)) except Exception as e: - raise MoulinetteError(m18n.g('_error_writing_file', file=file_path, error=str(e))) + raise MoulinetteError('_error_writing_file', file=file_path, error=str(e)) def mkdir(path, mode=0777, parents=False, uid=None, gid=None, force=False): @@ -206,14 +206,14 @@ def chown(path, uid=None, gid=None, recursive=False): try: uid = getpwnam(uid).pw_uid except KeyError: - raise MoulinetteError(m18n.g('unknown_user', user=uid)) + raise MoulinetteError('unknown_user', user=uid) elif uid is None: uid = -1 if isinstance(gid, basestring): try: gid = grp.getgrnam(gid).gr_gid except KeyError: - raise MoulinetteError(m18n.g('unknown_group', group=gid)) + raise MoulinetteError('unknown_group', group=gid) elif gid is None: gid = -1 @@ -226,7 +226,7 @@ def chown(path, uid=None, gid=None, recursive=False): for f in files: os.chown(os.path.join(root, f), uid, gid) except Exception as e: - raise MoulinetteError(m18n.g('error_changing_file_permissions', path=path, error=str(e))) + raise MoulinetteError('error_changing_file_permissions', path=path, error=str(e)) def chmod(path, mode, fmode=None, recursive=False): @@ -250,7 +250,7 @@ def chmod(path, mode, fmode=None, recursive=False): for f in files: os.chmod(os.path.join(root, f), fmode) except Exception as e: - raise MoulinetteError(m18n.g('error_changing_file_permissions', path=path, error=str(e))) + raise MoulinetteError('error_changing_file_permissions', path=path, error=str(e)) def rm(path, recursive=False, force=False): @@ -269,4 +269,4 @@ def rm(path, recursive=False, force=False): os.remove(path) except OSError as e: if not force: - raise MoulinetteError(m18n.g('error_removing', path=path, error=str(e))) + raise MoulinetteError('error_removing', path=path, error=str(e)) diff --git a/moulinette/utils/network.py b/moulinette/utils/network.py index fb87816e..6620ba71 100644 --- a/moulinette/utils/network.py +++ b/moulinette/utils/network.py @@ -1,7 +1,5 @@ -import errno import json -from moulinette import m18n from moulinette.core import MoulinetteError @@ -25,22 +23,22 @@ def download_text(url, timeout=30, expected_status_code=200): r = requests.get(url, timeout=timeout) # Invalid URL except requests.exceptions.ConnectionError: - raise MoulinetteError(m18n.g('invalid_url', url=url)) + raise MoulinetteError('invalid_url', url=url) # SSL exceptions except requests.exceptions.SSLError: - raise MoulinetteError(m18n.g('download_ssl_error', url=url)) + raise MoulinetteError('download_ssl_error', url=url) # Timeout exceptions except requests.exceptions.Timeout: - raise MoulinetteError(m18n.g('download_timeout', url=url)) + raise MoulinetteError('download_timeout', url=url) # Unknown stuff except Exception as e: - raise MoulinetteError(m18n.g('download_unknown_error', - url=url, error=str(e))) + raise MoulinetteError('download_unknown_error', + url=url, error=str(e)) # Assume error if status code is not 200 (OK) if expected_status_code is not None \ and r.status_code != expected_status_code: - raise MoulinetteError(m18n.g('download_bad_status_code', - url=url, code=str(r.status_code))) + raise MoulinetteError('download_bad_status_code', + url=url, code=str(r.status_code)) return r.text @@ -61,6 +59,6 @@ def download_json(url, timeout=30, expected_status_code=200): try: loaded_json = json.loads(text) except ValueError: - raise MoulinetteError(m18n.g('corrupted_json', ressource=url)) + raise MoulinetteError('corrupted_json', ressource=url) return loaded_json From df7ee42254696c98b8ed07810c2aaaacaa50fc8c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 12 Dec 2018 19:36:43 +0000 Subject: [PATCH 12/49] Allow to bypass m18n if a raw message is given --- moulinette/core.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/moulinette/core.py b/moulinette/core.py index 1bdd602a..894c85b8 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -412,8 +412,11 @@ def clean_session(session_id, profiles=[]): class MoulinetteError(StandardError): """Moulinette base exception""" - def __init__(self, key, *args, **kwargs): - msg = moulinette.m18n.g(key, *args, **kwargs) + def __init__(self, key, __raw_msg__=False, *args, **kwargs): + if __raw_msg__: + msg = key + else: + msg = moulinette.m18n.g(key, *args, **kwargs) super(MoulinetteError, self).__init__(msg) self.strerror = msg From a3436d114602989b3ad13d220c1469bdc055262f Mon Sep 17 00:00:00 2001 From: Josue-T Date: Thu, 13 Dec 2018 15:58:00 +0100 Subject: [PATCH 13/49] [fix] Automatically reconnect LDAP authenticator when slapd restarts (#185) * Fix LDAP authenticator when slapd was restarted * Update parameter for LDAP reconnection --- moulinette/authenticators/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 8484e491..0c3d4d7a 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -76,7 +76,7 @@ class Authenticator(BaseAuthenticator): def authenticate(self, password): try: - con = ldap.initialize(self.uri) + con = ldap.ldapobject.ReconnectLDAPObject(self.uri, retry_max=10, retry_delay=0.5) if self.userdn: con.simple_bind_s(self.userdn, password) else: From 22bfd1dbefaf605f1a5f869af8dce586487e9a47 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 13 Dec 2018 19:14:29 +0000 Subject: [PATCH 14/49] autopep8 + a few manual tweaks --- moulinette/actionsmap.py | 7 +++++++ moulinette/authenticators/__init__.py | 1 + moulinette/authenticators/ldap.py | 1 + moulinette/core.py | 11 +++++++++-- moulinette/interfaces/__init__.py | 5 ++++- moulinette/interfaces/api.py | 10 ++++++++-- moulinette/interfaces/cli.py | 3 +++ moulinette/utils/filesystem.py | 2 +- moulinette/utils/log.py | 2 ++ moulinette/utils/process.py | 2 +- moulinette/utils/serialize.py | 1 + moulinette/utils/stream.py | 2 ++ 12 files changed, 40 insertions(+), 7 deletions(-) diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 011a156a..4458fd44 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -25,6 +25,7 @@ logger = logging.getLogger('moulinette.actionsmap') # Extra parameters definition class _ExtraParameter(object): + """ Argument parser for an extra parameter. @@ -104,6 +105,7 @@ class CommentParameter(_ExtraParameter): class AskParameter(_ExtraParameter): + """ Ask for the argument value if possible and needed. @@ -138,6 +140,7 @@ class AskParameter(_ExtraParameter): class PasswordParameter(AskParameter): + """ Ask for the password argument value if possible and needed. @@ -159,6 +162,7 @@ class PasswordParameter(AskParameter): class PatternParameter(_ExtraParameter): + """ Check if the argument value match a pattern. @@ -204,6 +208,7 @@ class PatternParameter(_ExtraParameter): class RequiredParameter(_ExtraParameter): + """ Check if a required argument is defined or not. @@ -240,6 +245,7 @@ extraparameters_list = [CommentParameter, AskParameter, PasswordParameter, class ExtraArgumentParser(object): + """ Argument validator and parser for the extra parameters. @@ -357,6 +363,7 @@ def ordered_yaml_load(stream): class ActionsMap(object): + """Validate and process actions defined into an actions map The actions map defines the features - and their usage - of an diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index 8357bdb7..d4b902cd 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -12,6 +12,7 @@ logger = logging.getLogger('moulinette.authenticator') # Base Class ----------------------------------------------------------- class BaseAuthenticator(object): + """Authenticator base representation Each authenticators must implement an Authenticator class derived diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 1b5e034b..53d59f21 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -18,6 +18,7 @@ logger = logging.getLogger('moulinette.authenticator.ldap') # LDAP Class Implementation -------------------------------------------- class Authenticator(BaseAuthenticator): + """LDAP Authenticator Initialize a LDAP connexion for the given arguments. It attempts to diff --git a/moulinette/core.py b/moulinette/core.py index 894c85b8..c520427d 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -19,6 +19,7 @@ logger = logging.getLogger('moulinette.core') # Internationalization ------------------------------------------------- class Translator(object): + """Internationalization class Provide an internationalization mechanism based on JSON files to @@ -137,6 +138,7 @@ class Translator(object): class Moulinette18n(object): + """Internationalization service for the moulinette Manage internationalization and access to the proper keys translation @@ -214,6 +216,7 @@ class Moulinette18n(object): class MoulinetteSignals(object): + """Signals connector for the moulinette Allow to easily connect signals from the moulinette to handlers. A @@ -365,7 +368,7 @@ def init_interface(name, kwargs={}, actionsmap={}): return interface(amap, **kwargs) -def init_authenticator((vendor, name), kwargs={}): +def init_authenticator(vendor_and_name, kwargs={}): """Return a new authenticator instance Retrieve the given authenticator vendor and return a new instance of @@ -377,6 +380,7 @@ def init_authenticator((vendor, name), kwargs={}): - kwargs -- A dict of arguments for the authenticator profile """ + (vendor, name) = vendor_and_name try: mod = import_module('moulinette.authenticators.%s' % vendor) except ImportError: @@ -410,8 +414,10 @@ def clean_session(session_id, profiles=[]): # Moulinette core classes ---------------------------------------------- -class MoulinetteError(StandardError): +class MoulinetteError(Exception): + """Moulinette base exception""" + def __init__(self, key, __raw_msg__=False, *args, **kwargs): if __raw_msg__: msg = key @@ -422,6 +428,7 @@ class MoulinetteError(StandardError): class MoulinetteLock(object): + """Locker for a moulinette instance It provides a lock mechanism for a given moulinette instance. It can diff --git a/moulinette/interfaces/__init__.py b/moulinette/interfaces/__init__.py index fc7185e4..9bcc921a 100644 --- a/moulinette/interfaces/__init__.py +++ b/moulinette/interfaces/__init__.py @@ -19,6 +19,7 @@ CALLBACKS_PROP = '_callbacks' # Base Class ----------------------------------------------------------- class BaseActionsMapParser(object): + """Actions map's base Parser Each interfaces must implement an ActionsMapParser class derived @@ -352,6 +353,7 @@ class BaseActionsMapParser(object): class BaseInterface(object): + """Moulinette's base Interface Each interfaces must implement an Interface class derived from this @@ -424,7 +426,7 @@ class _CallbackAction(argparse.Action): value = self.callback(namespace, values, **self.callback_kwargs) except: logger.exception("cannot get value from callback method " - "'{0}'".format(self.callback_method)) + "'{0}'".format(self.callback_method)) raise MoulinetteError('error_see_log') else: if value: @@ -435,6 +437,7 @@ class _CallbackAction(argparse.Action): class _ExtendedSubParsersAction(argparse._SubParsersAction): + """Subparsers with extended properties for argparse It provides the following additional properties at initialization, diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 9cef8523..5e8e943c 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -57,11 +57,13 @@ def filter_csrf(callback): class LogQueues(dict): + """Map of session id to queue.""" pass class APIQueueHandler(logging.Handler): + """ A handler class which store logging records into a queue, to be used and retrieved from the API. @@ -87,6 +89,7 @@ class APIQueueHandler(logging.Handler): class _HTTPArgumentParser(object): + """Argument parser for HTTP requests Object for parsing HTTP requests into Python objects. It is based @@ -193,6 +196,7 @@ class _HTTPArgumentParser(object): class _ActionsMapPlugin(object): + """Actions map Bottle Plugin Process relevant action for the request using the actions map and @@ -538,11 +542,11 @@ def error_to_response(error): return HTTPUnauthorizedResponse(error.strerror) # Client-side error elif error.errno in [errno.ENOENT, errno.ESRCH, errno.ENXIO, errno.EEXIST, - errno.ENODEV, errno.EINVAL, errno.ENOPKG, errno.EDESTADDRREQ]: + errno.ENODEV, errno.EINVAL, errno.ENOPKG, errno.EDESTADDRREQ]: return HTTPBadRequestResponse(error.strerror) # Server-side error elif error.errno in [errno.EIO, errno.EBUSY, errno.ENODATA, errno.EINTR, - errno.ENETUNREACH]: + errno.ENETUNREACH]: return HTTPErrorResponse(error.strerror) else: logger.debug('unknown relevant response for error [%s] %s', @@ -571,6 +575,7 @@ def format_for_response(content): # API Classes Implementation ------------------------------------------- class ActionsMapParser(BaseActionsMapParser): + """Actions map's Parser for the API Provide actions map parsing methods for a CLI usage. The parser for @@ -707,6 +712,7 @@ class ActionsMapParser(BaseActionsMapParser): class Interface(BaseInterface): + """Application Programming Interface for the moulinette Initialize a HTTP server which serves the API connected to a given diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index c842813a..ce7a2726 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -177,6 +177,7 @@ def get_locale(): # CLI Classes Implementation ------------------------------------------- class TTYHandler(logging.StreamHandler): + """TTY log handler A handler class which prints logging records for a tty. The record is @@ -242,6 +243,7 @@ class TTYHandler(logging.StreamHandler): class ActionsMapParser(BaseActionsMapParser): + """Actions map's Parser for the CLI Provide actions map parsing methods for a CLI usage. The parser for @@ -369,6 +371,7 @@ class ActionsMapParser(BaseActionsMapParser): class Interface(BaseInterface): + """Command-line Interface for the moulinette Initialize an interface connected to the standard input/output diff --git a/moulinette/utils/filesystem.py b/moulinette/utils/filesystem.py index b984c785..521f93c5 100644 --- a/moulinette/utils/filesystem.py +++ b/moulinette/utils/filesystem.py @@ -144,7 +144,7 @@ def write_to_json(file_path, data): raise MoulinetteError('_error_writing_file', file=file_path, error=str(e)) -def mkdir(path, mode=0777, parents=False, uid=None, gid=None, force=False): +def mkdir(path, mode=0o777, parents=False, uid=None, gid=None, force=False): """Create a directory with optional features Create a directory and optionaly set its permissions to mode and its diff --git a/moulinette/utils/log.py b/moulinette/utils/log.py index 69eb62cf..d57c0b50 100644 --- a/moulinette/utils/log.py +++ b/moulinette/utils/log.py @@ -70,6 +70,7 @@ def getHandlersByClass(classinfo, limit=0): class MoulinetteLogger(Logger): + """Custom logger class Extend base Logger class to provide the SUCCESS custom log level with @@ -153,6 +154,7 @@ def getActionLogger(name=None, logger=None, action_id=None): class ActionFilter(object): + """Extend log record for an optionnal action Filter a given record and look for an `action_id` key. If it is not found diff --git a/moulinette/utils/process.py b/moulinette/utils/process.py index 5f1b0ddb..5f31bfce 100644 --- a/moulinette/utils/process.py +++ b/moulinette/utils/process.py @@ -60,7 +60,7 @@ def call_async_output(args, callback, **kwargs): if "stdinfo" in kwargs and kwargs["stdinfo"] is not None: assert len(callback) == 3 stdinfo = kwargs.pop("stdinfo") - os.mkfifo(stdinfo, 0600) + os.mkfifo(stdinfo, 0o600) # Open stdinfo for reading (in a nonblocking way, i.e. even # if command does not write in the stdinfo pipe...) stdinfo_f = os.open(stdinfo, os.O_RDONLY | os.O_NONBLOCK) diff --git a/moulinette/utils/serialize.py b/moulinette/utils/serialize.py index 0fbea631..06981d6e 100644 --- a/moulinette/utils/serialize.py +++ b/moulinette/utils/serialize.py @@ -9,6 +9,7 @@ logger = logging.getLogger('moulinette.utils.serialize') # JSON utilities ------------------------------------------------------- class JSONExtendedEncoder(JSONEncoder): + """Extended JSON encoder Extend default JSON encoder to recognize more types and classes. It diff --git a/moulinette/utils/stream.py b/moulinette/utils/stream.py index b11f7490..96d6fb5d 100644 --- a/moulinette/utils/stream.py +++ b/moulinette/utils/stream.py @@ -8,6 +8,7 @@ from multiprocessing.queues import SimpleQueue # Read from a stream --------------------------------------------------- class AsynchronousFileReader(Process): + """ Helper class to implement asynchronous reading of a file in a separate thread. Pushes read lines on a queue to @@ -74,6 +75,7 @@ class AsynchronousFileReader(Process): class Consummer(object): + def __init__(self, queue, callback): self.queue = queue self.callback = callback From fddf64a20d30b39b9c019edc773323ed21f02e64 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 14 Dec 2018 14:20:37 +0000 Subject: [PATCH 15/49] __raw_msg__ -> raw_msg --- moulinette/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moulinette/core.py b/moulinette/core.py index 894c85b8..24c72495 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -412,8 +412,8 @@ def clean_session(session_id, profiles=[]): class MoulinetteError(StandardError): """Moulinette base exception""" - def __init__(self, key, __raw_msg__=False, *args, **kwargs): - if __raw_msg__: + def __init__(self, key, raw_msg=False, *args, **kwargs): + if raw_msg: msg = key else: msg = moulinette.m18n.g(key, *args, **kwargs) From 8e91c0d0d36395543210c62e613eb8045b168f4e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 14 Dec 2018 16:21:48 +0000 Subject: [PATCH 16/49] Get rid of error_to_response and trigger 400 error for all MoulinetteErrors instead --- moulinette/interfaces/api.py | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 9cef8523..9f047d26 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -347,7 +347,7 @@ class _ActionsMapPlugin(object): self.logout(profile) except: pass - raise error_to_response(e) + raise HTTPUnauthorizedResponse(e.strerror) else: # Update dicts with new values s_hashes[profile] = s_hash @@ -434,7 +434,7 @@ class _ActionsMapPlugin(object): try: ret = self.actionsmap.process(arguments, timeout=30, route=_route) except MoulinetteError as e: - raise error_to_response(e) + raise HTTPBadRequestResponse(e.strerror) except Exception as e: if isinstance(e, HTTPResponse): raise e @@ -518,38 +518,12 @@ class HTTPUnauthorizedResponse(HTTPResponse): super(HTTPUnauthorizedResponse, self).__init__(output, 401) -class HTTPForbiddenResponse(HTTPResponse): - - def __init__(self, output=''): - super(HTTPForbiddenResponse, self).__init__(output, 403) - - class HTTPErrorResponse(HTTPResponse): def __init__(self, output=''): super(HTTPErrorResponse, self).__init__(output, 500) -def error_to_response(error): - """Convert a MoulinetteError to relevant HTTP response.""" - if error.errno == errno.EPERM: - return HTTPForbiddenResponse(error.strerror) - elif error.errno == errno.EACCES: - return HTTPUnauthorizedResponse(error.strerror) - # Client-side error - elif error.errno in [errno.ENOENT, errno.ESRCH, errno.ENXIO, errno.EEXIST, - errno.ENODEV, errno.EINVAL, errno.ENOPKG, errno.EDESTADDRREQ]: - return HTTPBadRequestResponse(error.strerror) - # Server-side error - elif error.errno in [errno.EIO, errno.EBUSY, errno.ENODATA, errno.EINTR, - errno.ENETUNREACH]: - return HTTPErrorResponse(error.strerror) - else: - logger.debug('unknown relevant response for error [%s] %s', - error.errno, error.strerror) - return HTTPErrorResponse(error.strerror) - - def format_for_response(content): """Format the resulted content of a request for the HTTP response.""" if request.method == 'POST': From 5ec49c1b8030d5b66af9153ad519bcc45751c6f6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 14 Dec 2018 16:24:04 +0000 Subject: [PATCH 17/49] StandardError -> Exception (for python3 compat') --- moulinette/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moulinette/core.py b/moulinette/core.py index 24c72495..47fedbbd 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -410,7 +410,7 @@ def clean_session(session_id, profiles=[]): # Moulinette core classes ---------------------------------------------- -class MoulinetteError(StandardError): +class MoulinetteError(Exception): """Moulinette base exception""" def __init__(self, key, raw_msg=False, *args, **kwargs): if raw_msg: From 1e90dd4bb1dc5f774a4a81b57c99800529100ab9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 18 Dec 2018 16:17:17 +0100 Subject: [PATCH 18/49] Make sure gpg actually does something (#182) --- moulinette/authenticators/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index d4b902cd..57d1a785 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -137,9 +137,13 @@ class BaseAuthenticator(object): """Store a session and its associated password""" gpg = gnupg.GPG() gpg.encoding = 'utf-8' + + # Encrypt the password using the session hash + s = str(gpg.encrypt(password, None, symmetric=True, passphrase=session_hash)) + assert len(s), "For some reason GPG can't perform encryption, maybe check /root/.gnupg/gpg.conf or re-run with gpg = gnupg.GPG(verbose=True) ?" + with self._open_sessionfile(session_id, 'w') as f: - f.write(str(gpg.encrypt(password, None, symmetric=True, - passphrase=session_hash))) + f.write(s) def _retrieve_session(self, session_id, session_hash): """Retrieve a session and return its associated password""" From e21bdb81ecdd6b17e6a781e76185297c59f9c4ec Mon Sep 17 00:00:00 2001 From: gdayon Date: Mon, 26 Nov 2018 22:08:31 +0000 Subject: [PATCH 19/49] [i18n] Translated using Weblate (Spanish) Currently translated at 100.0% (52 of 52 strings) --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 36fd562b..9b00b0db 100644 --- a/locales/es.json +++ b/locales/es.json @@ -49,5 +49,6 @@ "download_timeout": "{url:s} tardó demasiado en responder, me rindo.", "download_unknown_error": "Error al descargar datos desde {url:s} : {error:s}", "download_bad_status_code": "{url:s} devolvió el código de estado {code:s}", - "command_unknown": "Comando '{command:s}' desconocido ?" + "command_unknown": "Comando '{command:s}' desconocido ?", + "corrupted_yaml": "yaml corrupto leido desde {ressource:s} (motivo: {error:s})" } From b7ea1c4cf75f9d9aae50a70428e55d4c1c550c8d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 20 Dec 2018 20:54:22 +0000 Subject: [PATCH 20/49] Update changelog for 3.4.0 --- debian/changelog | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/debian/changelog b/debian/changelog index ede7e33f..1287ad6a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,19 @@ +moulinette (3.4.0) testing; urgency=low + + * [fix] Code cleaning with autopep8 (#187) + * [fix] Automatically reconnect LDAP authenticator when slapd restarts (#185) + * [enh] Display date as system timezone (#184) + * [mod] Make sure `gpg.encrypt` actually does something (#182) + * [mod] Add possiblity to get attribute name of conflict in LDAP (#181) + * [enh] Simplify moulinette error management (#180) + * [mod] Remove Access-Control-Allow-Origin (#174) + * [enh] Protect against CSRF (#171) + * [i18n] Improve Spanish translation + + Thanks to all contributors (Aleks, randomstuff, irina11y, Josue, gdayon, ljf) <3 ! + + -- Alexandre Aubin Thu, 20 Dec 2018 21:53:00 +0000 + moulinette (3.3.1) stable; urgency=low * [fix] 'force' semantics in 'utils.filesystem.mkdir' (#177) From 9c6424c46f4e350d2e6b2fcde170bc88fadd5b71 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 10 Jan 2019 07:09:04 +0100 Subject: [PATCH 21/49] Tiny typographic changes --- locales/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3c872c62..0091b232 100644 --- a/locales/en.json +++ b/locales/en.json @@ -46,10 +46,10 @@ "error_writing_file": "Error when writing file {file:s}: {error:s}", "error_removing": "Error when removing {path:s}: {error:s}", "error_changing_file_permissions": "Error when changing permissions for {path:s}: {error:s}", - "invalid_url": "Invalid url {url:s} (does this site exists ?)", + "invalid_url": "Invalid url {url:s} (does this site exists?)", "download_ssl_error": "SSL error when connecting to {url:s}", "download_timeout": "{url:s} took too long to answer, gave up.", - "download_unknown_error": "Error when downloading data from {url:s} : {error:s}", + "download_unknown_error": "Error when downloading data from {url:s}: {error:s}", "download_bad_status_code": "{url:s} returned status code {code:s}", - "command_unknown": "Command '{command:s}' unknown ?" + "command_unknown": "Command '{command:s}' unknown?" } From 5355c26927cd5a16b2bcb2bc7043f91c6bfa0f48 Mon Sep 17 00:00:00 2001 From: aleiyer Date: Sun, 6 Jan 2019 13:17:41 +0000 Subject: [PATCH 22/49] Added translation using Weblate (Chinese (Mandarin)) --- locales/cmn.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/cmn.json diff --git a/locales/cmn.json b/locales/cmn.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/cmn.json @@ -0,0 +1 @@ +{} From 70d1730209f46d112c2baaba76256365a428c001 Mon Sep 17 00:00:00 2001 From: aleiyer Date: Sun, 6 Jan 2019 13:19:24 +0000 Subject: [PATCH 23/49] [i18n] Translated using Weblate (Chinese (Mandarin)) Currently translated at 5.7% (3 of 52 strings) --- locales/cmn.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/cmn.json b/locales/cmn.json index 0967ef42..1097c487 100644 --- a/locales/cmn.json +++ b/locales/cmn.json @@ -1 +1,5 @@ -{} +{ + "argument_required": "争论{argument}是必须的", + "authentication_profile_required": "必须验证配置文件{profile}", + "authentication_required": "需要验证" +} From 7b2136a3a575b4be5bb61c0d48144e1cf6343cbe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 21:54:18 +0100 Subject: [PATCH 24/49] Update changelog for 3.4.1 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 1287ad6a..760a2a88 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +moulinette (3.4.1) testing; urgency=low + + * [i18n] Improve Chinese(Mandarin) translation + * [i18n] Misc orthotypograhy + + Thanks to all contributors (Jibec, aleiyer) <3 ! + + -- Alexandre Aubin Thu, 17 Jan 2019 21:50:00 +0000 + moulinette (3.4.0) testing; urgency=low * [fix] Code cleaning with autopep8 (#187) From 30c63adf6531bdc4949bd9acbdb338ed95c33959 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 18 Jan 2019 17:02:06 +0000 Subject: [PATCH 25/49] Added translation using Weblate (Basque) --- locales/eu.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/eu.json diff --git a/locales/eu.json b/locales/eu.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/eu.json @@ -0,0 +1 @@ +{} From 91df55ba86be3f12fdecae7091639d4e58c1edad Mon Sep 17 00:00:00 2001 From: Asier Garaialde Date: Fri, 18 Jan 2019 23:48:58 +0000 Subject: [PATCH 26/49] [i18n] Translated using Weblate (Basque) Currently translated at 1.9% (1 of 52 strings) --- locales/eu.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/eu.json b/locales/eu.json index 0967ef42..db0ce305 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1 +1,3 @@ -{} +{ + "argument_required": "'{argument}' argumentua beharrezkoa da" +} From d5c56f7896eacfab56b6a19936f28e50711919e2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Jan 2019 16:51:16 +0100 Subject: [PATCH 27/49] Update changelog for 3.4.2 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 760a2a88..c89b1d2a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +moulinette (3.4.2) stable; urgency=low + + * [i18n] Improve Basque translation + + Thanks to all contributors (A. Garaialde) <3 ! + + -- Alexandre Aubin Tue, 29 Jan 2019 16:50:00 +0000 + moulinette (3.4.1) testing; urgency=low * [i18n] Improve Chinese(Mandarin) translation From 0ec6194197031e5e0cc5aa26bf63a627d3f1d53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Mon, 4 Feb 2019 21:32:52 +0000 Subject: [PATCH 28/49] [i18n] Translated using Weblate (Russian) Currently translated at 46.1% (24 of 52 strings) --- locales/ru.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index 0967ef42..a35ef73f 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1 +1,26 @@ -{} +{ + "argument_required": "Требуется'{argument}' аргумент", + "authentication_profile_required": "Для доступа к '{profile}' требуется аутентификация", + "authentication_required": "Требуется аутентификация", + "authentication_required_long": "Для этого действия требуется аутентификация", + "colon": "{}: ", + "confirm": "Подтвердить {prompt}", + "deprecated_command": "'{prog} {command}' устарела и будет удалена", + "deprecated_command_alias": "'{prog} {old}' устарела и будет удалена, вместо неё используйте '{prog} {new}'", + "error": "Ошибка:", + "error_see_log": "Произошла ошибка. Пожалуйста, смотри подробности в логах, находящихся /var/log/yunohost/.", + "file_exists": "Файл уже существует: '{path}'", + "file_not_exist": "Файл не существует: '{path}'", + "folder_exists": "Каталог уже существует: '{path}'", + "folder_not_exist": "Каталог не существует", + "invalid_argument": "Неправильный аргумент '{argument}': {error}", + "invalid_password": "Неправильный пароль", + "ldap_attribute_already_exists": "Атрибут '{attribute}' уже существует со значением '{value}'", + "logged_in": "Вы вошли", + "logged_out": "Вы вышли из системы", + "not_logged_in": "Вы не залогинились", + "operation_interrupted": "Действие прервано", + "password": "Пароль", + "pattern_not_match": "Не соответствует образцу", + "server_already_running": "Сервер уже выполняется на этом порте" +} From 91704faeec6222d9bc8b6799dc51c03e26baccf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Thu, 7 Feb 2019 20:30:21 +0000 Subject: [PATCH 29/49] [i18n] Translated using Weblate (Russian) Currently translated at 73.0% (38 of 52 strings) --- locales/ru.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index a35ef73f..1bbc90b2 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -22,5 +22,19 @@ "operation_interrupted": "Действие прервано", "password": "Пароль", "pattern_not_match": "Не соответствует образцу", - "server_already_running": "Сервер уже выполняется на этом порте" + "server_already_running": "Сервер уже запущен на этом порте", + "success": "Отлично!", + "unable_authenticate": "Аутентификация невозможна", + "unknown_group": "Неизвестная '{group}' группа", + "unknown_user": "Неизвестный '{user}' пользователь", + "values_mismatch": "Неверные значения", + "warning": "Внимание :", + "websocket_request_expected": "Ожидается запрос WebSocket", + "cannot_open_file": "Не могу открыть файл {file:s} (причина: {error:s})", + "cannot_write_file": "Не могу записать файл {file:s} (причина: {error:s})", + "unknown_error_reading_file": "Неизвестная ошибка при чтении файла {file:s}", + "corrupted_yaml": "Повреждённой yaml получен от {ressource:s} (причина: {error:s})", + "error_writing_file": "Ошибка при записи файла {file:s}: {error:s}", + "error_removing": "Ошибка при удалении {path:s}: {error:s}", + "invalid_url": "Неправильный url {url:s} (dpэтотисайт существует ?)" } From 2f001e1c1f541b5b9358e9c5135580aa97bb777e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Thu, 7 Feb 2019 20:38:06 +0000 Subject: [PATCH 30/49] [i18n] Translated using Weblate (Russian) Currently translated at 78.8% (41 of 52 strings) --- locales/ru.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index 1bbc90b2..7713e044 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -36,5 +36,8 @@ "corrupted_yaml": "Повреждённой yaml получен от {ressource:s} (причина: {error:s})", "error_writing_file": "Ошибка при записи файла {file:s}: {error:s}", "error_removing": "Ошибка при удалении {path:s}: {error:s}", - "invalid_url": "Неправильный url {url:s} (dpэтотисайт существует ?)" + "invalid_url": "Неправильный url {url:s} (этот сайт существует ?)", + "download_ssl_error": "Ошибка SSL при соединении с {url:s}", + "download_timeout": "Превышено время ожидания ответа от {url:s}", + "download_unknown_error": "Ошибка при загрузке данных с {url:s} : {error:s}" } From 17a99e7ace1b84b00739a5444d2f51518a657829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Thu, 7 Feb 2019 20:41:00 +0000 Subject: [PATCH 31/49] [i18n] Translated using Weblate (Russian) Currently translated at 90.3% (47 of 52 strings) --- locales/ru.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 7713e044..af6373fa 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -38,6 +38,11 @@ "error_removing": "Ошибка при удалении {path:s}: {error:s}", "invalid_url": "Неправильный url {url:s} (этот сайт существует ?)", "download_ssl_error": "Ошибка SSL при соединении с {url:s}", - "download_timeout": "Превышено время ожидания ответа от {url:s}", - "download_unknown_error": "Ошибка при загрузке данных с {url:s} : {error:s}" + "download_timeout": "Превышено время ожидания ответа от {url:s}.", + "download_unknown_error": "Ошибка при загрузке данных с {url:s} : {error:s}", + "instance_already_running": "Процесс уже запущен", + "ldap_operation_error": "Ошибка в процессе работы LDAP", + "root_required": "Чтобы выполнить это действие, вы должны иметь права root", + "corrupted_json": "Повреждённой json получен от {ressource:s} (причина: {error:s})", + "command_unknown": "Команда '{command:s}' неизвестна ?" } From af1c3eb7c8673f83718309d32d778e6b687afd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Thu, 7 Feb 2019 20:49:41 +0000 Subject: [PATCH 32/49] [i18n] Translated using Weblate (Russian) Currently translated at 90.3% (47 of 52 strings) --- locales/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index af6373fa..0ef34009 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -43,6 +43,6 @@ "instance_already_running": "Процесс уже запущен", "ldap_operation_error": "Ошибка в процессе работы LDAP", "root_required": "Чтобы выполнить это действие, вы должны иметь права root", - "corrupted_json": "Повреждённой json получен от {ressource:s} (причина: {error:s})", + "corrupted_json": "Повреждённый json получен от {ressource:s} (причина: {error:s})", "command_unknown": "Команда '{command:s}' неизвестна ?" } From 32ce2e5ff5829046ca262da468e4ad07afcba8bb Mon Sep 17 00:00:00 2001 From: 1024 <9156571@qq.com> Date: Fri, 8 Mar 2019 00:55:11 +0000 Subject: [PATCH 33/49] [i18n] Translated using Weblate (Chinese (Mandarin)) Currently translated at 100.0% (52 of 52 strings) --- locales/cmn.json | 53 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/locales/cmn.json b/locales/cmn.json index 1097c487..bbad7315 100644 --- a/locales/cmn.json +++ b/locales/cmn.json @@ -1,5 +1,54 @@ { - "argument_required": "争论{argument}是必须的", + "argument_required": "{argument}是必须的", "authentication_profile_required": "必须验证配置文件{profile}", - "authentication_required": "需要验证" + "authentication_required": "需要验证", + "authentication_required_long": "此操作需要验证", + "colon": "{} ", + "confirm": "确认{prompt}", + "deprecated_command": "{prog}{command}已经放弃使用,将来会删除", + "deprecated_command_alias": "{prog}{old}已经放弃使用,将来会删除,请使用{prog}{new}代替", + "error": "错误:", + "error_see_log": "发生错误。请参看日志文件获取错误详情,日志文件位于 /var/log/yunohost/。", + "file_exists": "文件已存在:{path}", + "file_not_exist": "文件不存在:{path}", + "folder_exists": "目录已存在:{path}", + "folder_not_exist": "目录不存在", + "info": "信息:", + "instance_already_running": "实例已正在运行", + "invalid_argument": "参数错误{argument}:{error}", + "invalid_password": "密码错误", + "invalid_usage": "用法错误,输入 --help 查看帮助信息", + "ldap_attribute_already_exists": "参数{attribute}已赋值{value}", + "ldap_operation_error": "LDAP操作时发生了错误", + "ldap_server_down": "无法连接LDAP服务器", + "logged_in": "登录成功", + "logged_out": "注销成功", + "not_logged_in": "您未登录", + "operation_interrupted": "操作中断", + "password": "密码", + "pattern_not_match": "模式匹配失败", + "root_required": "必须以root身份进行此操作", + "server_already_running": "服务已运行在指定端口", + "success": "成功!", + "unable_authenticate": "认证失败", + "unable_retrieve_session": "获取会话失败", + "unknown_group": "未知组{group}", + "unknown_user": "未知用户{user}", + "values_mismatch": "值不匹配", + "warning": "警告:", + "websocket_request_expected": "期望一个WebSocket请求", + "cannot_open_file": "不能打开文件{file:s}(原因:{error:s})", + "cannot_write_file": "写入文件{file:s}失败(原因:{error:s})", + "unknown_error_reading_file": "尝试读取文件{file:s}时发生错误", + "corrupted_json": "json数据{ressource:s}读取失败(原因:{error:s})", + "corrupted_yaml": "读取yaml文件{ressource:s}失败(原因:{error:s})", + "error_writing_file": "写入文件{file:s}失败:{error:s}", + "error_removing": "删除路径{path:s}失败:{error:s}", + "error_changing_file_permissions": "目录{path:s}权限修改失败:{error:s}", + "invalid_url": "url:{url:s}无效(site是否存在?)", + "download_ssl_error": "连接{url:s}时发生SSL错误", + "download_timeout": "{url:s}响应超时,放弃。", + "download_unknown_error": "下载{url:s}失败:{error:s}", + "download_bad_status_code": "{url:s}返回状态码:{code:s}", + "command_unknown": "未知命令:{command:s}?" } From a2e45f0515b91ede0957bdb93081b35a93406c8a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 Mar 2019 17:22:39 +0100 Subject: [PATCH 34/49] Update changelog for 3.5.0 testing --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index c89b1d2a..a289e845 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +moulinette (3.5.0) testing; urgency=low + + * [i18n] Improve Russian and Chinese (Mandarin) translations + + Contributors : n3uz, Алексей + + -- Alexandre Aubin Wed, 13 Mar 2019 17:20:00 +0000 + moulinette (3.4.2) stable; urgency=low * [i18n] Improve Basque translation From 4503b15fcc3a4ae2850f18adda37a45f99024dd7 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 17 Mar 2019 13:25:59 +0000 Subject: [PATCH 35/49] Added translation using Weblate (Greek) --- locales/el.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/el.json diff --git a/locales/el.json b/locales/el.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/el.json @@ -0,0 +1 @@ +{} From 07bbbf60446437f090c2b53d9cc45bcb7bc4288b Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 17 Mar 2019 13:26:14 +0000 Subject: [PATCH 36/49] Added translation using Weblate (Hungarian) --- locales/hu.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/hu.json diff --git a/locales/hu.json b/locales/hu.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/hu.json @@ -0,0 +1 @@ +{} From 1ce2cdc6c1292a85bf84921c095915437df46b86 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 17 Mar 2019 13:26:25 +0000 Subject: [PATCH 37/49] Added translation using Weblate (Polish) --- locales/pl.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/pl.json diff --git a/locales/pl.json b/locales/pl.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/pl.json @@ -0,0 +1 @@ +{} From 531d73dc082936a298535150c86811c72f3e5279 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 17 Mar 2019 13:26:37 +0000 Subject: [PATCH 38/49] Added translation using Weblate (Swedish) --- locales/sv.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/sv.json diff --git a/locales/sv.json b/locales/sv.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/sv.json @@ -0,0 +1 @@ +{} From 6881509bd7058a4c56afed11c7112550fcab7fb0 Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 17 Mar 2019 16:47:53 +0000 Subject: [PATCH 39/49] Translated using Weblate (French) Currently translated at 84.6% (44 of 52 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/fr/ --- locales/fr.json | 55 +++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 59bca0f7..2cdc27e6 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,23 +1,23 @@ { - "argument_required": "L’argument « {argument} » est requis", - "authentication_profile_required": "L’authentification au profil « {profile} » requise", + "argument_required": "L’argument '{argument}' est requis", + "authentication_profile_required": "L’authentification au profil '{profile}' est requise", "authentication_required": "Authentification requise", "authentication_required_long": "L’authentification est requise pour exécuter cette action", "colon": "{} : ", "confirm": "Confirmez : {prompt}", - "deprecated_command": "« {prog} {command} » est déprécié et sera bientôt supprimé", - "deprecated_command_alias": "« {prog} {old} » est déprécié et sera bientôt supprimé, utilisez « {prog} {new} » à la place", + "deprecated_command": "'{prog} {command}' est déprécié et sera bientôt supprimé", + "deprecated_command_alias": "'{prog} {old}' est déprécié et sera bientôt supprimé, utilisez '{prog} {new}' à la place", "error": "Erreur :", - "error_see_log": "Une erreur est survenue. Veuillez consulter les journaux pour plus de détails, ils sont situés en /var/log/yunohost/.", - "file_exists": "Le fichier existe déjà : « {path} »", - "file_not_exist": "Le fichier « {path} » n’existe pas", - "folder_exists": "Le dossier existe déjà : « {path} »", + "error_see_log": "Une erreur est survenue. Veuillez consulter les journaux pour plus de détails, ils sont situés dans /var/log/yunohost/.", + "file_exists": "Le fichier existe déjà : '{path}'", + "file_not_exist": "Le fichier '{path}' n’existe pas", + "folder_exists": "Le dossier existe déjà : '{path}'", "folder_not_exist": "Le dossier n’existe pas", "instance_already_running": "Une instance est déjà en cours d’exécution", - "invalid_argument": "Argument « {argument} » incorrect : {error}", + "invalid_argument": "Argument '{argument}' incorrect : {error}", "invalid_password": "Mot de passe incorrect", - "invalid_usage": "Utilisation erronée, utilisez --help pour accéder à l’aide", - "ldap_attribute_already_exists": "L’attribut « {attribute} » existe déjà avec comme valeur : {value}", + "invalid_usage": "Utilisation erronée, utilisez le suffixe --help à la commande pour accéder à l’aide", + "ldap_attribute_already_exists": "L’attribut '{attribute}' existe déjà avec la valeur suivante : '{value}'", "ldap_operation_error": "Une erreur est survenue lors de l’opération LDAP", "ldap_server_down": "Impossible d’atteindre le serveur LDAP", "logged_in": "Connecté", @@ -25,30 +25,31 @@ "not_logged_in": "Vous n’êtes pas connecté", "operation_interrupted": "Opération interrompue", "password": "Mot de passe", - "pattern_not_match": "Ne correspond pas au motif", + "pattern_not_match": "Ne correspond pas au motif/modèle", "permission_denied": "Permission refusée", "root_required": "Vous devez être super-utilisateur pour exécuter cette action", "server_already_running": "Un serveur est déjà en cours d’exécution sur ce port", "success": "Succès !", "unable_authenticate": "Impossible de vous authentifier", "unable_retrieve_session": "Impossible de récupérer la session", - "unknown_group": "Groupe « {group} » inconnu", - "unknown_user": "Utilisateur « {user} » inconnu", + "unknown_group": "Groupe '{group}' inconnu", + "unknown_user": "L'utilisateur '{user}' est inconnu", "values_mismatch": "Les valeurs ne correspondent pas", "warning": "Attention :", - "websocket_request_expected": "Requête WebSocket attendue", - "cannot_open_file": "Impossible d’ouvrir le fichier {file:s} (cause : {error:s})", - "cannot_write_file": "Ne peut pas écrire le fichier {file:s} (cause : {error:s})", + "websocket_request_expected": "Une requête WebSocket est attendue", + "cannot_open_file": "Impossible d’ouvrir le fichier {file:s} pour la raison suivante : {error:s}", + "cannot_write_file": "Ne peut pas écrire le fichier {file:s} pour la raison suivante : {error:s}", "unknown_error_reading_file": "Erreur inconnue en essayant de lire le fichier {file:s}", - "corrupted_json": "Json corrompu lu depuis {ressource:s} (cause : {error:s})", - "error_writing_file": "Erreur en écrivant le fichier {file:s}:{error:s}", - "error_removing": "Erreur lors de la suppression {path:s}:{error:s}", - "error_changing_file_permissions": "Erreur lors de la modification des autorisations pour {path:s}:{error:s}", - "invalid_url": "Url invalide {url:s} (ce site existe-t-il ?)", + "corrupted_json": "Fichier JSON corrompu en lecture depuis {ressource:s} pour la raison suivante : {error:s}", + "error_writing_file": "Erreur en écrivant le fichier {file:s} : {error:s}", + "error_removing": "Erreur lors de la suppression {path:s} : {error:s}", + "error_changing_file_permissions": "Erreur lors de la modification des autorisations pour {path:s} : {error:s}", + "invalid_url": "URL {url:s} invalide : ce site existe-t-il ?", "download_ssl_error": "Erreur SSL lors de la connexion à {url:s}", - "download_timeout": "{url:s} a pris trop de temps pour répondre, abandon.", - "download_unknown_error": "Erreur lors du téléchargement des données à partir de {url:s}:{error:s}", - "download_bad_status_code": "{url:s} code de statut renvoyé {code:s}", - "command_unknown": "Commande « {command:s} » inconnue ?", - "corrupted_yaml": "YAML corrompu lu {ressource:s} depuis (cause : {error:s})" + "download_timeout": "{url:s} a pris trop de temps pour répondre : abandon.", + "download_unknown_error": "Erreur lors du téléchargement des données à partir de {url:s} : {error:s}", + "download_bad_status_code": "{url:s} renvoie le code d'état {code:s}", + "command_unknown": "Commande '{command:s}' inconnue ?", + "corrupted_yaml": "Syntaxe YAML corrompue en lecture depuis {ressource:s} pour la raison suivante : {error:s}", + "info": "Info :" } From 11a785db3a2e0fef98b4ce75f0a003b6534ecfd6 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 17 Mar 2019 16:17:39 +0000 Subject: [PATCH 40/49] Translated using Weblate (Catalan) Currently translated at 100.0% (52 of 52 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/ca/ --- locales/ca.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index 798fafb7..374f7a64 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -50,5 +50,6 @@ "download_timeout": "{url:s} ha tardat massa en respondre, s'ha deixat d'esperar.", "download_unknown_error": "Error al baixar dades des de {url:s}: {error:s}", "download_bad_status_code": "{url:s} ha retornat el codi d'estat {code:s}", - "command_unknown": "Ordre '{command:s}' desconegut ?" + "command_unknown": "Ordre '{command:s}' desconegut ?", + "info": "Info:" } From 0a300e50d771d5ed8d6d00ec9947c1d68d247233 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 01:03:06 +0100 Subject: [PATCH 41/49] [microdecision] Fix case where stdinfo is not provided --- moulinette/utils/process.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/moulinette/utils/process.py b/moulinette/utils/process.py index 5f31bfce..ccfc4873 100644 --- a/moulinette/utils/process.py +++ b/moulinette/utils/process.py @@ -65,7 +65,8 @@ def call_async_output(args, callback, **kwargs): # if command does not write in the stdinfo pipe...) stdinfo_f = os.open(stdinfo, os.O_RDONLY | os.O_NONBLOCK) else: - kwargs.pop("stdinfo") + if "stdinfo" in kwargs: + kwargs.pop("stdinfo") stdinfo = None # Validate callback argument @@ -98,13 +99,15 @@ def call_async_output(args, callback, **kwargs): # this way is not 100% perfect but should do it stdout_consum.process_next_line() stderr_consum.process_next_line() - stdinfo_consum.process_next_line() + if stdinfo: + stdinfo_consum.process_next_line() time.sleep(.1) stderr_reader.join() # clear the queues stdout_consum.process_current_queue() stderr_consum.process_current_queue() - stdinfo_consum.process_current_queue() + if stdinfo: + stdinfo_consum.process_current_queue() else: while not stdout_reader.eof(): stdout_consum.process_current_queue() From f6aa18cd63165cacbe8e86a21f8d13add9263965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Mon, 18 Mar 2019 18:42:16 +0000 Subject: [PATCH 42/49] Translated using Weblate (French) Currently translated at 100.0% (52 of 52 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/fr/ --- locales/fr.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 2cdc27e6..cd2c357a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -16,7 +16,7 @@ "instance_already_running": "Une instance est déjà en cours d’exécution", "invalid_argument": "Argument '{argument}' incorrect : {error}", "invalid_password": "Mot de passe incorrect", - "invalid_usage": "Utilisation erronée, utilisez le suffixe --help à la commande pour accéder à l’aide", + "invalid_usage": "Utilisation erronée, utilisez --help pour accéder à l’aide", "ldap_attribute_already_exists": "L’attribut '{attribute}' existe déjà avec la valeur suivante : '{value}'", "ldap_operation_error": "Une erreur est survenue lors de l’opération LDAP", "ldap_server_down": "Impossible d’atteindre le serveur LDAP", @@ -25,7 +25,7 @@ "not_logged_in": "Vous n’êtes pas connecté", "operation_interrupted": "Opération interrompue", "password": "Mot de passe", - "pattern_not_match": "Ne correspond pas au motif/modèle", + "pattern_not_match": "Ne correspond pas au motif", "permission_denied": "Permission refusée", "root_required": "Vous devez être super-utilisateur pour exécuter cette action", "server_already_running": "Un serveur est déjà en cours d’exécution sur ce port", @@ -33,14 +33,14 @@ "unable_authenticate": "Impossible de vous authentifier", "unable_retrieve_session": "Impossible de récupérer la session", "unknown_group": "Groupe '{group}' inconnu", - "unknown_user": "L'utilisateur '{user}' est inconnu", + "unknown_user": "L'utilisateur « {user} » est inconnu", "values_mismatch": "Les valeurs ne correspondent pas", "warning": "Attention :", "websocket_request_expected": "Une requête WebSocket est attendue", - "cannot_open_file": "Impossible d’ouvrir le fichier {file:s} pour la raison suivante : {error:s}", - "cannot_write_file": "Ne peut pas écrire le fichier {file:s} pour la raison suivante : {error:s}", + "cannot_open_file": "Impossible d’ouvrir le fichier {file:s} (raison : {error:s})", + "cannot_write_file": "Ne peut pas écrire le fichier {file:s} (raison : {error:s})", "unknown_error_reading_file": "Erreur inconnue en essayant de lire le fichier {file:s}", - "corrupted_json": "Fichier JSON corrompu en lecture depuis {ressource:s} pour la raison suivante : {error:s}", + "corrupted_json": "Fichier JSON corrompu en lecture depuis {ressource:s} (raison : {error:s})", "error_writing_file": "Erreur en écrivant le fichier {file:s} : {error:s}", "error_removing": "Erreur lors de la suppression {path:s} : {error:s}", "error_changing_file_permissions": "Erreur lors de la modification des autorisations pour {path:s} : {error:s}", @@ -50,6 +50,6 @@ "download_unknown_error": "Erreur lors du téléchargement des données à partir de {url:s} : {error:s}", "download_bad_status_code": "{url:s} renvoie le code d'état {code:s}", "command_unknown": "Commande '{command:s}' inconnue ?", - "corrupted_yaml": "Syntaxe YAML corrompue en lecture depuis {ressource:s} pour la raison suivante : {error:s}", - "info": "Info :" + "corrupted_yaml": "Fichier YAML corrompu en lecture depuis {ressource:s} (raison : {error:s})", + "info": "Info :" } From 968a8a82fd2b133f40a774b210f6e1aa5a90a3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sun, 17 Mar 2019 18:09:58 +0000 Subject: [PATCH 43/49] Translated using Weblate (Occitan) Currently translated at 100.0% (52 of 52 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/oc/ --- locales/oc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index 7ec0cfb6..64ebbf67 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -50,5 +50,6 @@ "download_bad_status_code": "{url:s} tòrna lo còdi d’estat {code:s}", "command_unknown": "Comanda {command:s} desconeguda ?", "corrupted_json": "Fichièr Json corromput legit de {ressource:s} (rason : {error:s})", - "corrupted_yaml": "Fichièr YAML corromput legit de {ressource:s} (rason : {error:s})" + "corrupted_yaml": "Fichièr YAML corromput legit de {ressource:s} (rason : {error:s})", + "info": "Info :" } From 06646931b1119d9f86fe3f255e988ac60382ebf7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 3 Apr 2019 02:27:18 +0200 Subject: [PATCH 44/49] Update changelog for 3.5.1 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index a289e845..e834e87a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +moulinette (3.5.1) testing; urgency=low + + * [fix] Fix case where stdinfo is not provided in call_async_output (0a300e5) + * [i18n] Improve translation for Greek, Hungarian, Polish, Swedish, French, Catalan, Occitan + + Thanks to all contributors (Aleks, ariasuni, Quenti, ppr, Xaloc) <3 ! + + -- Alexandre Aubin Wed, 03 Apr 2019 02:25:00 +0000 + moulinette (3.5.0) testing; urgency=low * [i18n] Improve Russian and Chinese (Mandarin) translations From 0e16546af9da06866a7c6f2b9c2cb94e0b4ba813 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 8 Apr 2019 18:15:00 +0200 Subject: [PATCH 45/49] [fix] Microdecision : do not miserably crash if the lock does not exist when attempting to release it --- moulinette/core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/moulinette/core.py b/moulinette/core.py index 50419b7b..c438a6c7 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -497,7 +497,10 @@ class MoulinetteLock(object): """ if self._locked: - os.unlink(self._lockfile) + if os.path.exists(self._lockfile): + os.unlink(self._lockfile) + else: + logger.warning("Uhoh, somehow the lock %s did not exist ..." % self._lockfile) logger.debug('lock has been released') self._locked = False From 63ef672e3dd226bca55d871acdcab0c878b682d2 Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Sat, 6 Apr 2019 10:40:50 +0000 Subject: [PATCH 46/49] Translated using Weblate (Arabic) Currently translated at 100.0% (52 of 52 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/ar/ --- locales/ar.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index f0ca8df8..8102b572 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -45,10 +45,11 @@ "error_removing": "خطأ أثناء عملية حذف {path:s}: {error:s}", "error_changing_file_permissions": "خطأ أثناء عملية تعديل التصريحات لـ {path:s}: {error:s}", "invalid_url": "خطأ في عنوان الرابط {url:s} (هل هذا الموقع موجود حقًا ؟)", - "download_ssl_error": "خطأ في الإتصال الآمن عبر الـ SSL أثناء محاولة الإتصال بـ {url:s}", + "download_ssl_error": "خطأ في الاتصال الآمن عبر الـ SSL أثناء محاولة الربط بـ {url:s}", "download_timeout": "{url:s} استغرق مدة طويلة جدا للإستجابة، فتوقّف.", "download_unknown_error": "خطأ أثناء عملية تنزيل البيانات مِن {url:s} : {error:s}", "download_bad_status_code": "{url:s} أعاد رمز الحالة {code:s}", "command_unknown": "الأمر '{command:s}' غير معروف ؟", - "corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource:s} (السبب : {error:s})" + "corrupted_yaml": "قراءة مُشوّهة لنسق yaml مِن {ressource:s} (السبب : {error:s})", + "info": "معلومة:" } From c32d0c29fa35bb6469f6a39dbb9fd1e15d276a56 Mon Sep 17 00:00:00 2001 From: Sylke Vicious Date: Sat, 6 Apr 2019 08:35:15 +0000 Subject: [PATCH 47/49] Translated using Weblate (Italian) Currently translated at 100.0% (52 of 52 strings) Translation: YunoHost/moulinette Translate-URL: https://translate.yunohost.org/projects/yunohost/moulinette/it/ --- locales/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index f27c89d3..479a1b96 100644 --- a/locales/it.json +++ b/locales/it.json @@ -50,5 +50,6 @@ "download_timeout": "{url:s} ci ha messo troppo a rispondere, abbandonato.", "download_unknown_error": "Errore durante il download di dati da {url:s} : {error:s}", "download_bad_status_code": "{url:s} ha restituito il codice di stato {code:s}", - "command_unknown": "Comando '{command:s}' sconosciuto ?" + "command_unknown": "Comando '{command:s}' sconosciuto ?", + "info": "Info:" } From d00c162fc034ee993ca906e2cfa7530cff43fd87 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 7 Apr 2019 21:16:14 +0000 Subject: [PATCH 48/49] Added translation using Weblate (Bengali (Bangladesh)) --- locales/bn_BD.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/bn_BD.json diff --git a/locales/bn_BD.json b/locales/bn_BD.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/bn_BD.json @@ -0,0 +1 @@ +{} From e61810760039ff7fae45d9b9f04a44cd7c025426 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Apr 2019 02:14:40 +0200 Subject: [PATCH 49/49] Update changelog for 3.5.2 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index e834e87a..b9322780 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +moulinette (3.5.2) stable; urgency=low + + - Release as stable ! + - [fix] Do not miserably crash if the lock does not exist when attempting to release it + - [i18n] Update translation for Arabic, Italian + + Thanks to all contributors (Aleks, BoF, silkevicious) <3 ! + + -- Alexandre Aubin Wed, 10 Apr 2019 02:14:00 +0000 + moulinette (3.5.1) testing; urgency=low * [fix] Fix case where stdinfo is not provided in call_async_output (0a300e5)