Merge branch 'dev' into rework-authenticator-system

This commit is contained in:
Alexandre Aubin 2021-06-13 15:34:22 +02:00
commit e2cb8cdfab
18 changed files with 271 additions and 385 deletions

View file

@ -45,13 +45,6 @@ Dev Documentation
https://moulinette.readthedocs.org
Requirements
------------
* Python 2.7
* python-bottle (>= 0.10)
* python-ldap (>= 2.4)
* PyYAML
Testing
-------

60
debian/changelog vendored
View file

@ -1,8 +1,62 @@
moulinette (4.2) unstable; urgency=low
moulinette (4.2.3.3) stable; urgency=low
- Placeholder for 4.2 to satisfy CI / debian build during dev
- [fix] Damn array args bug (2c9ec9f6)
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 20 Jan 2021 05:19:58 +0100
Thanks to all contributors <3 ! (ljf)
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 03 Jun 2021 18:40:18 +0200
moulinette (4.2.3.2) stable; urgency=low
- [fix] wait 1s for message in call_async_output, prevent CPU overload ([#275](https://github.com/YunoHost/moulinette/pull/275))
- [i18n] Translations updated for Chinese (Simplified)
Thanks to all contributors <3 ! (Kayou, yahoo)
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 02 Jun 2021 20:23:31 +0200
moulinette (4.2.3.1) stable; urgency=low
- [fix] Request params not decoded ([#277](https://github.com/YunoHost/moulinette/pull/277))
Thanks to all contributors <3 ! (ljf)
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 25 May 2021 18:59:01 +0200
moulinette (4.2.3) stable; urgency=low
- [fix] Unicode password doesn't log in ([#276](https://github.com/YunoHost/moulinette/pull/276))
- [i18n] Translations updated for Chinese (Simplified), Galician
Thanks to all contributors <3 ! (José M, ljf, yahoo)
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 24 May 2021 17:34:19 +0200
moulinette (4.2.2) stable; urgency=low
- [i18n] Translations updated for French, Hungarian
- Release as stable
Thanks to all contributors <3 ! (Dominik Blahó, Éric Gaspar)
-- Alexandre Aubin <alex.aubin@mailoo.org> Sat, 08 May 2021 15:10:01 +0200
moulinette (4.2.1) testing; urgency=low
- Fix weird technical thing in actionmap sucategories loading, related to recent changes in Yunohost actionmap (135fae95)
-- Alexandre Aubin <alex.aubin@mailoo.org> Sat, 17 Apr 2021 04:58:10 +0200
moulinette (4.2.0) testing; urgency=low
- [mod] Python2 -> python3 ([#228](https://github.com/YunoHost/moulinette/pull/228), 8e70561f, 570e5323, 3758b811, 90f894b5, [#269](https://github.com/YunoHost/moulinette/pull/269), e85b9f71, cafe68f3)
- [mod] Code formatting, test fixing, cleanup (677efcf6, 0de15467, [#268](https://github.com/YunoHost/moulinette/pull/268), affb54f8, f7199f7a, d6f82c91)
- [enh] Improve error semantic such that the webadmin shall be able to redirect to the proper log view ([#257](https://github.com/YunoHost/moulinette/pull/257), [#271](https://github.com/YunoHost/moulinette/pull/271))
- [fix] Simpler and more consistent logging initialization ([#263](https://github.com/YunoHost/moulinette/pull/263))
Thanks to all contributors <3 ! (Kay0u, Laurent Peuch)
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 19 Mar 2021 18:39:42 +0100
moulinette (4.1.4) stable; urgency=low

View file

@ -1,181 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses
"""
"""
Generate JSON specification files API
"""
import os
import sys
import yaml
import json
import requests
from yunohost import str_to_func, __version__
def main():
"""
"""
with open('action_map.yml') as f:
action_map = yaml.load(f)
try:
with open('/etc/yunohost/current_host', 'r') as f:
domain = f.readline().rstrip()
except IOError:
domain = requests.get('http://ip.yunohost.org').text
with open('action_map.yml') as f:
action_map = yaml.load(f)
resource_list = {
'apiVersion': __version__,
'swaggerVersion': '1.1',
'basePath': 'http://'+ domain + ':6767',
'apis': []
}
resources = {}
del action_map['general_arguments']
for category, category_params in action_map.items():
if 'category_help' not in category_params: category_params['category_help'] = ''
resource_path = '/api/'+ category
resource_list['apis'].append({
'path': resource_path,
'description': category_params['category_help']
})
resources[category] = {
'apiVersion': __version__,
'swaggerVersion': '1.1',
'basePath': 'http://'+ domain + ':6767',
'apis': []
}
resources[category]['resourcePath'] = resource_path
registered_paths = {}
for action, action_params in category_params['actions'].items():
if 'action_help' not in action_params:
action_params['action_help'] = ''
if 'api' not in action_params:
action_params['api'] = 'GET /'+ category +'/'+ action
method, path = action_params['api'].split(' ')
key_param = ''
if '{' in path:
key_param = path[path.find("{")+1:path.find("}")]
notes = ''
if str_to_func('yunohost_'+ category +'.'+ category +'_'+ action) is None:
notes = 'Not yet implemented'
operation = {
'httpMethod': method,
'nickname': category +'_'+ action,
'summary': action_params['action_help'],
'notes': notes,
'errorResponses': []
}
if 'arguments' in action_params:
operation['parameters'] = []
for arg_name, arg_params in action_params['arguments'].items():
if 'help' not in arg_params:
arg_params['help'] = ''
param_type = 'query'
allow_multiple = False
required = True
allowable_values = None
name = arg_name.replace('-', '_')
if name[0] == '_':
required = False
if 'full' in arg_params:
name = arg_params['full'][2:]
else:
name = name[2:]
name = name.replace('-', '_')
if 'nargs' in arg_params:
if arg_params['nargs'] == '*':
allow_multiple = True
required = False
if arg_params['nargs'] == '+':
allow_multiple = True
required = True
else:
allow_multiple = False
if 'choices' in arg_params:
allowable_values = {
'valueType': 'LIST',
'values': arg_params['choices']
}
if 'action' in arg_params and arg_params['action'] == 'store_true':
allowable_values = {
'valueType': 'LIST',
'values': ['true', 'false']
}
if name == key_param:
param_type = 'path'
required = True
allow_multiple = False
parameters = {
'paramType': param_type,
'name': name,
'description': arg_params['help'],
'dataType': 'string',
'required': required,
'allowMultiple': allow_multiple
}
if allowable_values is not None:
parameters['allowableValues'] = allowable_values
operation['parameters'].append(parameters)
if path in registered_paths:
resources[category]['apis'][registered_paths[path]]['operations'].append(operation)
resources[category]['apis'][registered_paths[path]]['description'] = ''
else:
registered_paths[path] = len(resources[category]['apis'])
resources[category]['apis'].append({
'path': path,
'description': action_params['action_help'],
'operations': [operation]
})
try: os.listdir(os.getcwd() +'/doc')
except OSError: os.makedirs(os.getcwd() +'/doc')
for category, api_dict in resources.items():
with open(os.getcwd() +'/doc/'+ category +'.json', 'w') as f:
json.dump(api_dict, f)
with open(os.getcwd() +'/doc/resources.json', 'w') as f:
json.dump(resource_list, f)
if __name__ == '__main__':
sys.exit(main())

View file

@ -1,114 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses
"""
"""
Generate function header documentation
"""
import os
import sys
import yaml
import re
def main():
"""
"""
with open('action_map.yml') as f:
action_map = yaml.load(f)
resources = {}
del action_map['general_arguments']
for category, category_params in action_map.items():
if 'category_help' not in category_params: category_params['category_help'] = ''
with open('yunohost_'+ category +'.py', 'r') as f:
lines = f.readlines()
with open('yunohost_'+ category +'.py', 'w') as f:
in_block = False
for line in lines:
if in_block:
if re.search(r'^"""', line):
in_block = False
f.write('\n')
f.write(' '+ category_params['category_help'] +'\n')
f.write('"""\n')
else:
f.write(line)
if re.search(r'^""" yunohost_'+ category, line):
in_block = True
for action, action_params in category_params['actions'].items():
if 'action_help' not in action_params:
action_params['action_help'] = ''
help_lines = [
' """',
' '+ action_params['action_help'],
''
]
if 'arguments' in action_params:
help_lines.append(' Keyword argument:')
for arg_name, arg_params in action_params['arguments'].items():
if 'help' in arg_params:
help = ' -- '+ arg_params['help']
else:
help = ''
name = arg_name.replace('-', '_')
if name[0] == '_':
required = False
if 'full' in arg_params:
name = arg_params['full'][2:]
else:
name = name[2:]
name = name.replace('-', '_')
help_lines.append(' '+ name + help)
help_lines.append('')
help_lines.append(' """')
with open('yunohost_'+ category +'.py', 'r') as f:
lines = f.readlines()
with open('yunohost_'+ category +'.py', 'w') as f:
in_block = False
first_quotes = True
for line in lines:
if in_block:
if re.search(r'^ """', line):
if first_quotes:
first_quotes = False
else:
in_block = False
for help_line in help_lines:
f.write(help_line +'\n')
else:
f.write(line)
if re.search(r'^def '+ category +'_'+ action +'\(', line):
in_block = True
if __name__ == '__main__':
sys.exit(main())

View file

@ -7,11 +7,11 @@
"confirm": "确认 {prompt}",
"deprecated_command": "{prog}{command}已经放弃使用,将来会删除",
"deprecated_command_alias": "{prog}{old}已经放弃使用,将来会删除,请使用{prog}{new}代替",
"error": "错误",
"error": "错误:",
"error_see_log": "发生错误。请参看日志文件获取错误详情,日志文件位于 /var/log/yunohost/。",
"file_exists": "文件已存在{path}",
"file_not_exist": "文件不存在{path}",
"folder_exists": "目录已存在{path}",
"file_exists": "文件已存在: '{path}'",
"file_not_exist": "文件不存在: '{path}'",
"folder_exists": "目录已存在: '{path}'",
"folder_not_exist": "目录不存在",
"info": "信息:",
"instance_already_running": "已经有一个YunoHost操作正在运行。 请等待它完成再运行另一个。",

View file

@ -1,3 +1,58 @@
{
"password": "Heslo"
"password": "Heslo",
"logged_out": "Jste odhlášen/a",
"ldap_server_is_down_restart_it": "LDAP služba neběží, probíhá pokus o její nastartování...",
"warn_the_user_that_lock_is_acquired": "Předchozí operace dokončena, nyní spouštíme tuto",
"warn_the_user_about_waiting_lock_again": "Stále čekáme...",
"warn_the_user_about_waiting_lock": "Jiná YunoHost operace právě probíhá, před spuštěním této čekáme na její dokončení",
"command_unknown": "Příkaz '{command:s}' neznámý?",
"download_bad_status_code": "{url:s} vrátil stavový kód {code:s}",
"download_unknown_error": "Chyba při stahování dat z {url:s}: {error:s}",
"download_timeout": "{url:s} příliš dlouho neodpovídá, akce přerušena.",
"download_ssl_error": "SSL chyba při spojení s {url:s}",
"invalid_url": "Špatný odkaz {url:s} (je vůbec dostupný?)",
"error_changing_file_permissions": "Chyba při nastavování oprávnění pro {path:s}: {error:s}",
"error_removing": "Chyba při přesunu {path:s}: {error:s}",
"error_writing_file": "Chyba při zápisu souboru/ů {file:s}: {error:s}",
"corrupted_toml": "Nepodařilo se načíst TOML z {ressource:s} (reason: {error:s})",
"corrupted_yaml": "Nepodařilo se načíst YAML z {ressource:s} (reason: {error:s})",
"corrupted_json": "Nepodařilo se načíst JSON {ressource:s} (reason: {error:s})",
"unknown_error_reading_file": "Vyskytla se neznámá chyba při čtení souboru/ů {file:s} (reason: {error:s})",
"cannot_write_file": "Nelze zapsat soubor/y {file:s} (reason: {error:s})",
"cannot_open_file": "Nelze otevřít soubor/y {file:s} (reason: {error:s})",
"websocket_request_expected": "Očekáván WebSocket požadavek",
"warning": "Varování:",
"values_mismatch": "Hodnoty nesouhlasí",
"unknown_user": "Neznámý '{user}' uživatel",
"unknown_group": "Neznámá '{group}' skupina",
"session_expired": "Sezení vypršelo. Přihlašte se znovu, prosím.",
"unable_retrieve_session": "Není možné obdržet sezení neboť '{exception}'",
"unable_authenticate": "Není možné ověřit",
"success": "Zadařilo se!",
"server_already_running": "Na tomto portu je server již provozován",
"root_required": "Pro provedení této akce musíte být root",
"pattern_not_match": "Neodpovídá výrazu",
"operation_interrupted": "Operace přerušena",
"not_logged_in": "Nejste přihlášen",
"logged_in": "Přihlášení",
"ldap_server_down": "Spojení s LDAP serverem se nezdařilo",
"ldap_attribute_already_exists": "Atribut '{attribute}' již obsahuje hodnotu '{value}'",
"invalid_usage": "Nesprávné použití, pass --help pro zobrazení nápovědy",
"invalid_token": "Nesprávný token - ověřte se prosím",
"invalid_password": "Nesprávné heslo",
"invalid_argument": "Nesprávný argument '{argument}': {error}",
"instance_already_running": "Právě probíhá jiná YunoHost operace. Před spuštěním další operace vyčkejte na její dokončení.",
"info": "Info:",
"folder_not_exist": "Adresář neexistuje",
"folder_exists": "Adresář již existuje: '{path}'",
"file_not_exist": "Soubor neexistuje: '{path}'",
"file_exists": "Soubor již existuje: '{path}'",
"error": "Chyba:",
"deprecated_command_alias": "'{prog} {old}' je zastaralý a bude odebrán v budoucích verzích, použijte '{prog} {new}'",
"deprecated_command": "'{prog} {command}' je zastaralý a bude odebrán v budoucích verzích",
"confirm": "Potvrdit {prompt}",
"colon": "{}: ",
"authentication_required_long": "K provedení této akce je vyžadováno ověření",
"authentication_required": "Vyžadováno ověření",
"argument_required": "Je vyžadován argument '{argument}'"
}

1
locales/fi.json Normal file
View file

@ -0,0 +1 @@
{}

58
locales/gl.json Normal file
View file

@ -0,0 +1,58 @@
{
"ldap_attribute_already_exists": "O atributo '{attribute}' xa existe e ten o valor '{value}'",
"invalid_usage": "Uso non válido, pass --help para ver a axuda",
"invalid_token": "Token non válido - por favor autentícate",
"invalid_password": "Contrasinal non válido",
"invalid_argument": "Argumento non válido '{argument}': {error}",
"instance_already_running": "Hai unha operación de YunoHost en execución. Por favor agarda a que remate antes de realizar unha nova.",
"info": "Info:",
"folder_not_exist": "O cartafol non existe",
"folder_exists": "Xa existe o cartafol: '{path}'",
"file_not_exist": "Non existe o ficheiro: '{path}'",
"file_exists": "Xa existe o ficheiro: '{path}'",
"error": "Erro:",
"deprecated_command_alias": "'{prog} {old}' xa non se utiliza e será eliminado no futuro, usa '{prog} {new}' no seu lugar",
"deprecated_command": "'{prog} {command}' xa non se utiliza e xa non se usará no futuro",
"confirm": "Confirma {prompt}",
"colon": "{}: ",
"authentication_required_long": "Requírese autenticación para realizar esta acción",
"authentication_required": "Autenticación requerida",
"argument_required": "O argumento '{argument}' é requerido",
"logged_out": "Sesión pechada",
"password": "Contrasinal",
"warning": "Aviso:",
"values_mismatch": "Non concordan os valores",
"unknown_user": "Usuaria '{user}' descoñecida",
"unknown_group": "Grupo '{group}' descoñecido",
"session_expired": "A sesión caducou. Volve a conectar por favor.",
"unable_retrieve_session": "Non se puido obter a sesión porque '{exception}'",
"unable_authenticate": "Non se puido autenticar",
"success": "Ben feito!",
"server_already_running": "Xa hai un servidor a funcionar nese porto",
"root_required": "Tes que ser root para facer esta acción",
"pattern_not_match": "Non concorda co patrón",
"operation_interrupted": "Interrumpeuse a operación",
"not_logged_in": "Non estás conectada",
"logged_in": "Conectada",
"ldap_server_down": "Non se puido acadar o servidor LDAP",
"ldap_server_is_down_restart_it": "O servizo LDAP está caído, intentando reinicialo...",
"warn_the_user_that_lock_is_acquired": "O outro comando rematou, agora executarase este",
"warn_the_user_about_waiting_lock_again": "Agardando...",
"warn_the_user_about_waiting_lock": "Estase executando outro comando de YunoHost neste intre, estamos agardando a que remate para executar este",
"command_unknown": "Comando '{command:s}' descoñecido?",
"download_bad_status_code": "{url:s} devolveu o código de estado {code:s}",
"download_unknown_error": "Erro ao descargar os datos desde {url:s}: {error:s}",
"download_timeout": "{url:s} está tardando en responder, deixámolo.",
"download_ssl_error": "Erro SSL ao conectar con {url:s}",
"invalid_url": "URL non válido {url:s} (existe esta web?)",
"error_changing_file_permissions": "Erro ao cambiar os permisos de {path:s}: {error:s}",
"error_removing": "Erro ao eliminar {path:s}: {error:s}",
"error_writing_file": "Erro ao escribir o ficheiro {file:s}: {error:s}",
"corrupted_toml": "Lectura corrupta de datos TOML de {ressource:s} (razón: {error:s})",
"corrupted_yaml": "Lectura corrupta dos datos YAML de {ressource:s} (razón: {error:s})",
"corrupted_json": "Lectura corrupta dos datos JSON de {ressource:s} (razón: {error:s})",
"unknown_error_reading_file": "Erro descoñecido ao intentar ler o ficheiro {file:s} (razón: {error:s})",
"cannot_write_file": "Non se puido escribir o ficheiro {file:s} (razón: {error:s})",
"cannot_open_file": "Non se puido abrir o ficheiro {file:s} (razón: {error:s})",
"websocket_request_expected": "Agardábase unha solicitude WebSocket"
}

View file

@ -6,14 +6,14 @@
"colon": "{}: ",
"confirm": "पुष्टि करें {prompt}",
"deprecated_command": "'{prog}' '{command}' का प्रयोग न करे, भविष्य में इसे हटा दिया जाएगा",
"deprecated_command_alias": "'{prog} {old}' का प्रयोग न करे ,भविष्य में इसे हटा दिया जाएगा।इस की जगह '{prog} {new}' का प्रोयोग करे।",
"deprecated_command_alias": "'{prog} {old}' अब पुराना हो गया है और इसे भविष्य में हटा दिया जाएगा, इस की जगह '{prog} {new}' का प्रयोग करें",
"error": "गलती:",
"error_see_log": "एक त्रुटि पाई गई। कृपया विवरण के लिए लॉग देखें।",
"file_exists": "फ़ाइल पहले से ही मौजूद है:'{path}'",
"file_not_exist": "फ़ाइल मौजूद नहीं है: '{path}'",
"folder_exists": "फ़ोल्डर में पहले से ही मौजूद है: '{path}'",
"folder_not_exist": "फ़ोल्डर मौजूद नहीं है",
"instance_already_running": "आवृत्ति पहले से चल रही है।",
"folder_not_exist": "फ़ोल्डर मौजूद नहीं है",
"instance_already_running": "यूनोहोस्ट का एक कार्य पहले से चल रहा है। कृपया इस कार्य के समाप्त होने का इंतज़ार करें।",
"invalid_argument": "अवैध तर्क '{argument}':'{error}'",
"invalid_password": "अवैध पासवर्ड",
"invalid_usage": "अवैध उपयोग, सहायता देखने के लिए --help साथ लिखे।",
@ -35,5 +35,6 @@
"unknown_user": "अज्ञात उपयोगकर्ता: '{user}'",
"values_mismatch": "वैल्यूज मेल नहीं खाती।",
"warning": "चेतावनी:",
"websocket_request_expected": "एक WebSocket अनुरोध की उम्मीद।"
"websocket_request_expected": "एक WebSocket अनुरोध की उम्मीद।",
"info": "सूचना:"
}

View file

@ -1,4 +1,19 @@
{
"logged_out": "Kilépett",
"password": "Jelszó"
"password": "Jelszó",
"download_timeout": "{url:s} régóta nem válaszol, folyamat megszakítva.",
"invalid_url": "Helytelen URL: {url:s} (biztos létezik az oldal?)",
"cannot_open_file": "{file:s} megnyitása sikertelen (Oka: {error:s})",
"unknown_user": "Ismeretlen felhasználó: '{user}'",
"unknown_group": "Ismeretlen csoport: '{group}'",
"server_already_running": "Egy szerver már fut ezen a porton",
"logged_in": "Bejelentkezve",
"success": "Siker!",
"values_mismatch": "Eltérő értékek",
"warning": "Figyelem:",
"invalid_password": "Helytelen jelszó",
"info": "Információ:",
"file_not_exist": "A fájl nem létezik: '{path}'",
"file_exists": "A fájl már létezik: '{path}'",
"error": "Hiba:"
}

View file

@ -1,10 +1,10 @@
{
"logged_out": "Wylogowano",
"password": "Hasło",
"warn_the_user_that_lock_is_acquired": "drugie polecenie właśnie się zakończyło, teraz uruchamiając to polecenie",
"warn_the_user_that_lock_is_acquired": "Inne polecenie właśnie się zakończyło, teraz uruchamiam to polecenie",
"warn_the_user_about_waiting_lock_again": "Wciąż czekam...",
"warn_the_user_about_waiting_lock": "Kolejne polecenie YunoHost jest teraz uruchomione, czekamy na jego zakończenie przed uruchomieniem tego",
"command_unknown": "Polecenie „{command:s}” jest nieznane?",
"command_unknown": "Polecenie '{command:s}' jest nieznane?",
"download_bad_status_code": "{url:s} zwrócił kod stanu {code:s}",
"download_unknown_error": "Błąd podczas pobierania danych z {url:s}: {error:s}",
"download_timeout": "{url:s} odpowiedział zbyt długo, poddał się.",
@ -13,17 +13,17 @@
"error_changing_file_permissions": "Błąd podczas zmiany uprawnień dla {path:s}: {error:s}",
"error_removing": "Błąd podczas usuwania {path:s}: {error:s}",
"error_writing_file": "Błąd podczas zapisywania pliku {file:s}: {error:s}",
"corrupted_toml": "Uszkodzony toml z {ressource: s} (powód: {error:s})",
"corrupted_yaml": "Uszkodzony yaml odczytany z {ressource:s} (powód: {error:s})",
"corrupted_json": "Uszkodzony json odczytany z {ressource:s} (powód: {error:s})",
"corrupted_toml": "Uszkodzony TOML z {ressource: s} (reason: {error:s})",
"corrupted_yaml": "Uszkodzony YAML odczytany z {ressource:s} (reason: {error:s})",
"corrupted_json": "Uszkodzony JSON odczytany z {ressource:s} (reason: {error:s})",
"unknown_error_reading_file": "Nieznany błąd podczas próby odczytania pliku {file:s} (przyczyna: {error:s})",
"cannot_write_file": "Nie można zapisać pliku {file:s} (przyczyna: {error:s})",
"cannot_open_file": "Nie można otworzyć pliku {file:s} (przyczyna: {error:s})",
"websocket_request_expected": "Oczekiwano żądania WebSocket",
"warning": "Ostrzeżenie:",
"values_mismatch": "Wartości nie pasują",
"unknown_user": "Nieznany użytkownik „{user}”",
"unknown_group": "Nieznana grupa „{group}”",
"unknown_user": "Nieznany użytkownik '{user}'",
"unknown_group": "Nieznana grupa '{group}'",
"unable_retrieve_session": "Nie można pobrać sesji, ponieważ „{exception}”",
"unable_authenticate": "Nie można uwierzytelnić",
"success": "Sukces!",
@ -53,5 +53,7 @@
"colon": "{}: ",
"authentication_required_long": "Do wykonania tej czynności wymagane jest uwierzytelnienie",
"authentication_required": "Wymagane uwierzytelnienie",
"argument_required": "Argument „{argument}” jest wymagany"
"argument_required": "Argument „{argument}” jest wymagany",
"ldap_server_is_down_restart_it": "Usługa LDAP nie działa, próba restartu...",
"session_expired": "Sesja wygasła. Zaloguj się ponownie."
}

View file

@ -14,7 +14,12 @@ from importlib import import_module
from moulinette import m18n, msignals
from moulinette.cache import open_cachefile
from moulinette.globals import init_moulinette_env
from moulinette.core import MoulinetteError, MoulinetteLock
from moulinette.core import (
MoulinetteError,
MoulinetteLock,
MoulinetteAuthenticationError,
MoulinetteValidationError,
)
from moulinette.interfaces import BaseActionsMapParser, GLOBAL_SECTION, TO_RETURN_PROP
from moulinette.utils.log import start_action_logging
@ -207,7 +212,9 @@ class PatternParameter(_ExtraParameter):
if msg == message:
msg = m18n.g(message)
raise MoulinetteError("invalid_argument", argument=arg_name, error=msg)
raise MoulinetteValidationError(
"invalid_argument", argument=arg_name, error=msg
)
return arg_value
@staticmethod
@ -238,7 +245,7 @@ class RequiredParameter(_ExtraParameter):
def __call__(self, required, arg_name, arg_value):
if required and (arg_value is None or arg_value == ""):
logger.warning("argument '%s' is required", arg_name)
raise MoulinetteError("argument_required", argument=arg_name)
raise MoulinetteValidationError("argument_required", argument=arg_name)
return arg_value
@staticmethod
@ -491,7 +498,7 @@ class ActionsMap(object):
authenticator = self.get_authenticator(auth_method)
if not msignals.authenticate(authenticator):
raise MoulinetteError("authentication_required_long")
raise MoulinetteAuthenticationError("authentication_required_long")
def process(self, args, timeout=None, **kwargs):
"""
@ -771,6 +778,9 @@ class ActionsMap(object):
# No parser for the action
continue
if action_parser is None: # No parser for the action
continue
# Store action identifier and add arguments
action_parser.set_defaults(_tid=tid)
action_parser.add_arguments(

View file

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

View file

@ -370,6 +370,8 @@ class MoulinetteSignals(object):
class MoulinetteError(Exception):
http_code = 500
"""Moulinette base exception"""
def __init__(self, key, raw_msg=False, *args, **kwargs):
@ -384,6 +386,16 @@ class MoulinetteError(Exception):
return self.strerror
class MoulinetteValidationError(MoulinetteError):
http_code = 400
class MoulinetteAuthenticationError(MoulinetteError):
http_code = 401
class MoulinetteLock(object):
"""Locker for a moulinette instance

View file

@ -15,7 +15,7 @@ from bottle import abort
from moulinette import msignals, m18n, env
from moulinette.actionsmap import ActionsMap
from moulinette.core import MoulinetteError
from moulinette.core import MoulinetteError, MoulinetteValidationError
from moulinette.interfaces import (
BaseActionsMapParser,
BaseInterface,
@ -207,8 +207,7 @@ class _HTTPArgumentParser(object):
return self._parser.dequeue_callbacks(*args, **kwargs)
def _error(self, message):
# TODO: Raise a proper exception
raise MoulinetteError(message, raw_msg=True)
raise MoulinetteValidationError(message, raw_msg=True)
class _ActionsMapPlugin(object):
@ -250,9 +249,9 @@ class _ActionsMapPlugin(object):
def wrapper():
kwargs = {}
try:
kwargs["password"] = request.POST["password"]
kwargs["password"] = request.POST.password
except KeyError:
raise HTTPBadRequestResponse("Missing password parameter")
raise HTTPResponse("Missing password parameter", 400)
kwargs["profile"] = request.POST.get("profile", self.actionsmap.default_authentication)
return callback(**kwargs)
@ -321,7 +320,7 @@ class _ActionsMapPlugin(object):
for a in args:
params[a] = True
# Append other request params
for k, v in request.params.dict.items():
for k, v in request.params.decode().dict.items():
v = _format(v)
if k not in params.keys():
params[k] = v
@ -387,7 +386,7 @@ class _ActionsMapPlugin(object):
self.logout(profile)
except:
pass
raise HTTPUnauthorizedResponse(e.strerror)
raise HTTPResponse(e.strerror, 401)
else:
# Update dicts with new values
s_tokens[profile] = s_new_token
@ -420,7 +419,7 @@ class _ActionsMapPlugin(object):
if profile not in request.get_cookie(
"session.tokens", secret=s_secret, default={}
):
raise HTTPUnauthorizedResponse(m18n.g("not_logged_in"))
raise HTTPResponse(m18n.g("not_logged_in"), 401)
else:
del self.secrets[s_id]
authenticator = self.actionsmap.get_authenticator(profile)
@ -448,7 +447,7 @@ class _ActionsMapPlugin(object):
wsock = request.environ.get("wsgi.websocket")
if not wsock:
raise HTTPErrorResponse(m18n.g("websocket_request_expected"))
raise HTTPResponse(m18n.g("websocket_request_expected"), 500)
while True:
item = queue.get()
@ -485,7 +484,7 @@ class _ActionsMapPlugin(object):
try:
ret = self.actionsmap.process(arguments, timeout=30, route=_route)
except MoulinetteError as e:
raise HTTPBadRequestResponse(e)
raise moulinette_error_to_http_response(e)
except Exception as e:
if isinstance(e, HTTPResponse):
raise e
@ -493,7 +492,7 @@ class _ActionsMapPlugin(object):
tb = traceback.format_exc()
logs = {"route": _route, "arguments": arguments, "traceback": tb}
return HTTPErrorResponse(json_encode(logs))
return HTTPResponse(json_encode(logs), 500)
else:
return format_for_response(ret)
finally:
@ -521,7 +520,7 @@ class _ActionsMapPlugin(object):
]
except KeyError:
msg = m18n.g("authentication_required")
raise HTTPUnauthorizedResponse(msg)
raise HTTPResponse(msg, 401)
else:
return authenticator(token=(s_id, s_token))
@ -548,36 +547,17 @@ class _ActionsMapPlugin(object):
# HTTP Responses -------------------------------------------------------
class HTTPOKResponse(HTTPResponse):
def __init__(self, output=""):
super(HTTPOKResponse, self).__init__(output, 200)
def moulinette_error_to_http_response(error):
class HTTPBadRequestResponse(HTTPResponse):
def __init__(self, error=""):
if isinstance(error, MoulinetteError):
content = error.content()
if isinstance(content, dict):
super(HTTPBadRequestResponse, self).__init__(
return HTTPResponse(
json_encode(content),
400,
error.http_code,
headers={"Content-type": "application/json"},
)
else:
super(HTTPBadRequestResponse, self).__init__(content, 400)
else:
super(HTTPBadRequestResponse, self).__init__(error, 400)
class HTTPUnauthorizedResponse(HTTPResponse):
def __init__(self, output=""):
super(HTTPUnauthorizedResponse, self).__init__(output, 401)
class HTTPErrorResponse(HTTPResponse):
def __init__(self, output=""):
super(HTTPErrorResponse, self).__init__(output, 500)
return HTTPResponse(content, error.http_code)
def format_for_response(content):
@ -692,7 +672,7 @@ class ActionsMapParser(BaseActionsMapParser):
except KeyError as e:
error_message = "no argument parser found for route '%s': %s" % (route, e)
logger.error(error_message)
raise MoulinetteError(error_message, raw_msg=True)
raise MoulinetteValidationError(error_message, raw_msg=True)
return parser.authentication
@ -709,7 +689,7 @@ class ActionsMapParser(BaseActionsMapParser):
except KeyError as e:
error_message = "no argument parser found for route '%s': %s" % (route, e)
logger.error(error_message)
raise MoulinetteError(error_message, raw_msg=True)
raise MoulinetteValidationError(error_message, raw_msg=True)
ret = argparse.Namespace()
# TODO: Catch errors?

View file

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

View file

@ -77,7 +77,7 @@ def call_async_output(args, callback, **kwargs):
while True:
try:
callback, message = log_queue.get_nowait()
callback, message = log_queue.get(True, 1)
except queue.Empty:
break