From 51171b84bf0a8c30b46170ee7a040bc6d655f54b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Sep 2019 22:24:49 +0200 Subject: [PATCH 01/92] main.metronome -> main.xmpp --- data/other/ldap_scheme.yml | 4 ++-- data/templates/metronome/domain.tpl.cfg.lua | 2 +- src/yunohost/backup.py | 4 ++-- src/yunohost/tests/test_permission.py | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index 11504bbe8..d013149af 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -67,8 +67,8 @@ depends_children: - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" - cn=main.metronome,ou=permission: - cn: main.metronome + cn=main.xmpp,ou=permission: + cn: main.xmpp gidNumber: "5002" objectClass: - posixGroup diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/data/templates/metronome/domain.tpl.cfg.lua index 2ee9cfaae..d523365db 100644 --- a/data/templates/metronome/domain.tpl.cfg.lua +++ b/data/templates/metronome/domain.tpl.cfg.lua @@ -8,7 +8,7 @@ VirtualHost "{{ domain }}" hostname = "localhost", user = { basedn = "ou=users,dc=yunohost,dc=org", - filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.metronome,ou=permission,dc=yunohost,dc=org))", + filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.xmpp,ou=permission,dc=yunohost,dc=org))", usernamefield = "mail", namefield = "cn", }, diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index bd5d5750d..55b6678b8 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1191,7 +1191,7 @@ class RestoreManager(): old_apps_permission = [] try: old_apps_permission = ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))', + '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.xmpp))(!(cn=main.sftp)))', ['cn', 'objectClass', 'groupPermission', 'URL', 'gidNumber']) except: logger.info(m18n.n('apps_permission_not_found')) @@ -1247,7 +1247,7 @@ class RestoreManager(): # Remove all permission for all app which sill in the LDAP for per in ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))', + '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.xmpp))(!(cn=main.sftp)))', ['cn']): if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]): raise YunohostError('permission_deletion_failed', diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index d309a8211..3b9815f63 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -135,7 +135,7 @@ def check_LDAP_db_integrity(): def check_permission_for_apps(): # We check that the for each installed apps we have at last the "main" permission # and we don't have any permission linked to no apps. The only exception who is not liked to an app - # is mail, metronome, and sftp + # is mail, xmpp, and sftp from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -146,7 +146,7 @@ def check_permission_for_apps(): installed_apps = {app['id'] for app in app_list(installed=True)['apps']} permission_list_set = {permission['cn'][0].split(".")[1] for permission in permission_search} - extra_service_permission = set(['mail', 'metronome']) + extra_service_permission = set(['mail', 'xmpp']) if 'sftp' in permission_list_set: extra_service_permission.add('sftp') assert installed_apps == permission_list_set - extra_service_permission @@ -164,8 +164,8 @@ def test_list_permission(): assert "main" in res['blog'] assert "mail" in res assert "main" in res['mail'] - assert "metronome" in res - assert "main" in res['metronome'] + assert "xmpp" in res + assert "main" in res['xmpp'] assert ["all_users"] == res['wiki']['main']['allowed_groups'] assert ["alice"] == res['blog']['main']['allowed_groups'] assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) @@ -220,9 +220,9 @@ def test_remove_bad_permission(): assert "blog" in res assert "main" in res['blog'] assert "mail" in res - assert "main" in res ['mail'] - assert "metronome" in res - assert "main" in res['metronome'] + assert "main" in res['mail'] + assert "xmpp" in res + assert "main" in res['xmpp'] def test_remove_main_permission(): with pytest.raises(YunohostError): From 0f688caccd33f425059081d0c283afce075e328b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Sep 2019 22:55:37 +0200 Subject: [PATCH 02/92] Swap 'main' in permission namespace --- data/other/ldap_scheme.yml | 8 ++++---- data/templates/dovecot/dovecot-ldap.conf | 4 ++-- data/templates/metronome/domain.tpl.cfg.lua | 2 +- data/templates/postfix/plain/ldap-accounts.cf | 2 +- data/templates/postfix/plain/ldap-aliases.cf | 2 +- src/yunohost/app.py | 2 +- src/yunohost/backup.py | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index d013149af..caa8fffb2 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -59,16 +59,16 @@ children: - groupOfNamesYnh depends_children: - cn=main.mail,ou=permission: - cn: main.mail + cn=mail.main,ou=permission: + cn: mail.main gidNumber: "5001" objectClass: - posixGroup - permissionYnh groupPermission: - "cn=all_users,ou=groups,dc=yunohost,dc=org" - cn=main.xmpp,ou=permission: - cn: main.xmpp + cn=xmpp.main,ou=permission: + cn: xmpp.main gidNumber: "5002" objectClass: - posixGroup diff --git a/data/templates/dovecot/dovecot-ldap.conf b/data/templates/dovecot/dovecot-ldap.conf index c7c9785fd..3a80ba47f 100644 --- a/data/templates/dovecot/dovecot-ldap.conf +++ b/data/templates/dovecot/dovecot-ldap.conf @@ -3,7 +3,7 @@ auth_bind = yes ldap_version = 3 base = ou=users,dc=yunohost,dc=org user_attrs = uidNumber=500,gidNumber=8,mailuserquota=quota_rule=*:bytes=%$ -user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) -pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) +user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org)) +pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org)) default_pass_scheme = SSHA diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/data/templates/metronome/domain.tpl.cfg.lua index d523365db..e7f6bcef7 100644 --- a/data/templates/metronome/domain.tpl.cfg.lua +++ b/data/templates/metronome/domain.tpl.cfg.lua @@ -8,7 +8,7 @@ VirtualHost "{{ domain }}" hostname = "localhost", user = { basedn = "ou=users,dc=yunohost,dc=org", - filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.xmpp,ou=permission,dc=yunohost,dc=org))", + filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=xmpp.main,ou=permission,dc=yunohost,dc=org))", usernamefield = "mail", namefield = "cn", }, diff --git a/data/templates/postfix/plain/ldap-accounts.cf b/data/templates/postfix/plain/ldap-accounts.cf index 9f6f94e6d..75f38cf58 100644 --- a/data/templates/postfix/plain/ldap-accounts.cf +++ b/data/templates/postfix/plain/ldap-accounts.cf @@ -1,5 +1,5 @@ server_host = localhost server_port = 389 search_base = dc=yunohost,dc=org -query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) +query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org)) result_attribute = uid diff --git a/data/templates/postfix/plain/ldap-aliases.cf b/data/templates/postfix/plain/ldap-aliases.cf index 5e7d3a6c1..46563ae22 100644 --- a/data/templates/postfix/plain/ldap-aliases.cf +++ b/data/templates/postfix/plain/ldap-aliases.cf @@ -1,5 +1,5 @@ server_host = localhost server_port = 389 search_base = dc=yunohost,dc=org -query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) +query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org)) result_attribute = maildrop diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4a14c5e4b..105d4faf7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -432,7 +432,7 @@ def app_map(app=None, raw=False, user=None): if user is not None: ldap = _get_ldap_interface() if not ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=main.%s)(inheritPermission=uid=%s,ou=users,dc=yunohost,dc=org))' % (app_id, user), + filter='(&(objectclass=permissionYnh)(cn=%s.main)(inheritPermission=uid=%s,ou=users,dc=yunohost,dc=org))' % (app_id, user), attrs=['cn']): continue diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 55b6678b8..9a27031ae 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1247,7 +1247,7 @@ class RestoreManager(): # Remove all permission for all app which sill in the LDAP for per in ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.xmpp))(!(cn=main.sftp)))', + '(&(objectClass=permissionYnh)(!(cn=mail.main))(!(cn=xmpp.main))(!(cn=sftp.main)))', ['cn']): if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]): raise YunohostError('permission_deletion_failed', From 34df84e222e04a975e0315e8155f5045ba9b9f0c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 00:57:32 +0200 Subject: [PATCH 03/92] group add -> group create, to be consistent with user create/delete --- data/actionsmap/yunohost.yml | 4 ++-- locales/en.json | 2 +- .../data_migrations/0011_setup_group_permission.py | 4 ++-- src/yunohost/tests/test_user-group.py | 12 ++++++------ src/yunohost/user.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index cbb7756b0..8a6c10b5f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -212,8 +212,8 @@ user: help: fields to fetch nargs: "+" - ### user_group_add() - add: + ### user_group_create() + create: action_help: Create group api: POST /users/groups arguments: diff --git a/locales/en.json b/locales/en.json index be00d5b1e..815c40b75 100644 --- a/locales/en.json +++ b/locales/en.json @@ -288,7 +288,7 @@ "log_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", - "log_user_group_add": "Add '{}' group", + "log_user_group_create": "Create '{}' group", "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 17fd8f4a5..9699d2cd9 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -7,7 +7,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration -from yunohost.user import user_group_add, user_group_update +from yunohost.user import user_group_create, user_group_update from yunohost.app import app_setting, app_list from yunohost.regenconf import regen_conf from yunohost.permission import permission_add, permission_sync_to_user @@ -65,7 +65,7 @@ class MyMigration(Migration): username = user_info['uid'][0] ldap.update('uid=%s,ou=users' % username, {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) - user_group_add(username, gid=user_info['uidNumber'][0], sync_perm=False) + user_group_create(username, gid=user_info['uidNumber'][0], sync_perm=False) user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=False) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 3973f0c7d..1defce466 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,7 +1,7 @@ import pytest from moulinette.core import MoulinetteError -from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_add, user_group_delete, user_group_update, user_group_info +from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_create, user_group_delete, user_group_update, user_group_info from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity @@ -24,8 +24,8 @@ def setup_function(function): user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") user_create("jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh") - user_group_add("dev") - user_group_add("apps") + user_group_create("dev") + user_group_create("apps") user_group_update("dev", add_user=["alice"]) user_group_update("apps", add_user=["bob"]) @@ -83,7 +83,7 @@ def test_del_user(): assert "alice" not in group_res['all_users']['members'] def test_add_group(): - user_group_add("adminsys") + user_group_create("adminsys") group_res = user_group_list()['groups'] assert "adminsys" in group_res @@ -122,12 +122,12 @@ def test_del_bad_user_1(): def test_add_bad_group_1(): # Check groups already exist with special group "all_users" with pytest.raises(YunohostError): - user_group_add("all_users") + user_group_create("all_users") def test_add_bad_group_2(): # Check groups already exist (for standard groups) with pytest.raises(MoulinetteError): - user_group_add("dev") + user_group_create("dev") def test_del_bad_group_1(): # Check not allowed to remove this groups diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a6c262ed7..e5480ca92 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -218,7 +218,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, exc_info=1) # Create group for user and add to group 'all_users' - user_group_add(groupname=username, gid=uid, sync_perm=False) + user_group_create(groupname=username, gid=uid, sync_perm=False) user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=True) @@ -554,7 +554,7 @@ def user_group_list(fields=None): @is_unit_operation([('groupname', 'user')]) -def user_group_add(operation_logger, groupname, gid=None, sync_perm=True): +def user_group_create(operation_logger, groupname, gid=None, sync_perm=True): """ Create group From f60af2053f959d02fe958310ff9eaa40a9877be3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 01:06:20 +0200 Subject: [PATCH 04/92] permission_add/remove becomes create/delete to be consistent with user and group create/delete. In the context of permissions, add/remove shall instead be related to adding/removing an existing permission for a user or group. --- data/helpers.d/setting | 4 ++-- src/yunohost/app.py | 10 +++++----- src/yunohost/backup.py | 4 ++-- .../0011_setup_group_permission.py | 4 ++-- src/yunohost/permission.py | 4 ++-- src/yunohost/tests/test_permission.py | 20 +++++++++---------- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index da711b4bd..e002afa57 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -249,7 +249,7 @@ ynh_permission_create() { if [[ -n ${urls:-} ]]; then urls=",urls=['${urls//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_add; permission_add('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -263,7 +263,7 @@ ynh_permission_remove() { local permission ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove('$app', '$permission', sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app', '$permission', sync_perm=False)" } # Add a path managed by the SSO diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 105d4faf7..c2fb87acf 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -738,7 +738,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import permission_add, permission_update, permission_remove, permission_sync_to_user + from yunohost.permission import permission_create, permission_update, permission_delete, permission_sync_to_user ldap = _get_ldap_interface() # Fetch or extract sources @@ -875,7 +875,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Create permission before the install (useful if the install script redefine the permission) # Note that sync_perm is disabled to avoid triggering a whole bunch of code and messages # can't be sure that we don't have one case when it's needed - permission_add(app=app_instance_name, permission="main", sync_perm=False) + permission_create(app=app_instance_name, permission="main", sync_perm=False) # Execute the app install script install_retcode = 1 @@ -914,7 +914,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) permission_list = [p['cn'][0] for p in result] for l in permission_list: - permission_remove(app_instance_name, l.split('.')[0], force=True) + permission_delete(app_instance_name, l.split('.')[0], force=True) if remove_retcode != 0: msg = m18n.n('app_not_properly_removed', @@ -980,7 +980,7 @@ def app_remove(operation_logger, app): """ from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_exec, hook_remove, hook_callback - from yunohost.permission import permission_remove, permission_sync_to_user + from yunohost.permission import permission_delete, permission_sync_to_user if not _is_installed(app): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) @@ -1031,7 +1031,7 @@ def app_remove(operation_logger, app): filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app, attrs=['cn']) permission_list = [p['cn'][0] for p in result] for l in permission_list: - permission_remove(app, l.split('.')[0], force=True, sync_perm=False) + permission_delete(app, l.split('.')[0], force=True, sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 9a27031ae..9e90ece2a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1303,7 +1303,7 @@ class RestoreManager(): """ from moulinette.utils.filesystem import read_ldif from yunohost.user import user_group_list - from yunohost.permission import permission_remove + from yunohost.permission import permission_delete from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -1441,7 +1441,7 @@ class RestoreManager(): filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) permission_list = [p['cn'][0] for p in result] for l in permission_list: - permission_remove(app_instance_name, l.split('.')[0], force=True) + permission_delete(app_instance_name, l.split('.')[0], force=True) # TODO Cleaning app hooks else: diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 9699d2cd9..d0bae2d95 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -10,7 +10,7 @@ from yunohost.tools import Migration from yunohost.user import user_group_create, user_group_update from yunohost.app import app_setting, app_list from yunohost.regenconf import regen_conf -from yunohost.permission import permission_add, permission_sync_to_user +from yunohost.permission import permission_create, permission_sync_to_user from yunohost.user import user_permission_add logger = getActionLogger('yunohost.migration') @@ -85,7 +85,7 @@ class MyMigration(Migration): domain = app_setting(app, 'domain') urls = [domain + path] if domain and path else None - permission_add(app, permission='main', urls=urls, default_allow=True, sync_perm=False) + permission_create(app, permission='main', urls=urls, default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_add([app], permission='main', group=allowed_group, sync_perm=False) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index f7fa307da..3f9131018 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -317,7 +317,7 @@ def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=T @is_unit_operation(['permission', 'app']) -def permission_add(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): +def permission_create(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): """ Create a new permission for a specific application @@ -431,7 +431,7 @@ def permission_update(operation_logger, app, permission, add_url=None, remove_ur @is_unit_operation(['permission', 'app']) -def permission_remove(operation_logger, app, permission, force=False, sync_perm=True): +def permission_delete(operation_logger, app, permission, force=False, sync_perm=True): """ Remove a permission for a specific application diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 3b9815f63..8222248b1 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -3,7 +3,7 @@ import pytest from moulinette.core import MoulinetteError from yunohost.app import app_install, app_remove, app_change_url, app_list from yunohost.user import user_list, user_create, user_permission_list, user_delete, user_group_list, user_group_delete, user_permission_add, user_permission_remove, user_permission_clear -from yunohost.permission import permission_add, permission_update, permission_remove +from yunohost.permission import permission_create, permission_update, permission_delete from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError @@ -21,15 +21,15 @@ def clean_user_groups_permission(): for a, per in user_permission_list()['permissions'].items(): if a in ['wiki', 'blog', 'site']: for p in per: - permission_remove(a, p, force=True, sync_perm=False) + permission_delete(a, p, force=True, sync_perm=False) def setup_function(function): clean_user_groups_permission() user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") - permission_add("wiki", "main", [maindomain + "/wiki"], sync_perm=False) - permission_add("blog", "main", sync_perm=False) + permission_create("wiki", "main", [maindomain + "/wiki"], sync_perm=False) + permission_create("blog", "main", sync_perm=False) user_permission_add(["blog"], "main", group="alice") @@ -177,7 +177,7 @@ def test_list_permission(): # def test_add_permission_1(): - permission_add("site", "test") + permission_create("site", "test") res = user_permission_list()['permissions'] assert "site" in res @@ -186,7 +186,7 @@ def test_add_permission_1(): assert set(["alice", "bob"]) == set(res['site']['test']['allowed_users']) def test_add_permission_2(): - permission_add("site", "main", default_allow=False) + permission_create("site", "main", default_allow=False) res = user_permission_list()['permissions'] assert "site" in res @@ -195,7 +195,7 @@ def test_add_permission_2(): assert [] == res['site']['main']['allowed_users'] def test_remove_permission(): - permission_remove("wiki", "main", force=True) + permission_delete("wiki", "main", force=True) res = user_permission_list()['permissions'] assert "wiki" not in res @@ -207,12 +207,12 @@ def test_remove_permission(): def test_add_bad_permission(): # Create permission with same name with pytest.raises(YunohostError): - permission_add("wiki", "main") + permission_create("wiki", "main") def test_remove_bad_permission(): # Remove not existant permission with pytest.raises(MoulinetteError): - permission_remove("non_exit", "main", force=True) + permission_delete("non_exit", "main", force=True) res = user_permission_list()['permissions'] assert "wiki" in res @@ -226,7 +226,7 @@ def test_remove_bad_permission(): def test_remove_main_permission(): with pytest.raises(YunohostError): - permission_remove("blog", "main") + permission_delete("blog", "main") res = user_permission_list()['permissions'] assert "mail" in res From a6d68c76c4eaa998db3fa77e7ece0c811e85e8ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 01:31:22 +0200 Subject: [PATCH 05/92] permission_update -> permission_urls (+ tweak the helper name) so that it's more differentiable from user_permission_update --- data/helpers.d/setting | 10 +++++----- src/yunohost/permission.py | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index e002afa57..e6311fc1f 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -268,18 +268,18 @@ ynh_permission_remove() { # Add a path managed by the SSO # -# usage: ynh_permission_add_path --app "app" --permission "permission" --url "url" ["url" ...] +# usage: ynh_permission_add_url --app "app" --permission "permission" --url "url" ["url" ...] # | arg: app - the application id # | arg: permission - the name for the permission # | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) -ynh_permission_add_path() { +ynh_permission_add_url() { declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) local app local permission local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" } # Remove a path managed by the SSO @@ -288,12 +288,12 @@ ynh_permission_add_path() { # | arg: app - the application id # | arg: permission - the name for the permission # | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) -ynh_permission_del_path() { +ynh_permission_remove_url() { declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) local app local permission local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" } diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 3f9131018..1a5465674 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -35,6 +35,12 @@ from yunohost.log import is_unit_operation logger = getActionLogger('yunohost.user') +# +# +# The followings are the methods exposed through the "yunohost user permission" interface +# +# + def user_permission_list(app=None, permission=None, username=None, group=None): """ @@ -315,6 +321,14 @@ def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=T return user_permission_list(app, permission) +# +# +# The followings methods are *not* directly exposed. +# They are used to create/delete the permissions (e.g. during app install/remove) +# and by some app helpers to possibly add additional permissions and tweak the urls +# +# + @is_unit_operation(['permission', 'app']) def permission_create(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): @@ -374,9 +388,9 @@ def permission_create(operation_logger, app, permission, urls=None, default_allo @is_unit_operation(['permission', 'app']) -def permission_update(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True): +def permission_urls(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True): """ - Update a permission for a specific application + Update urls related to a permission for a specific application Keyword argument: app -- an application OR sftp, xmpp (metronome), mail From 0f7b8c3515e80afe648d55445517db60d848f609 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 02:08:46 +0200 Subject: [PATCH 06/92] Simplify group list interface and code --- data/actionsmap/yunohost.yml | 11 ++++-- src/yunohost/backup.py | 2 +- src/yunohost/permission.py | 12 +++--- src/yunohost/user.py | 76 +++++++++++++----------------------- src/yunohost/utils/ldap.py | 14 +++++++ 5 files changed, 55 insertions(+), 60 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 8a6c10b5f..b463df646 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -208,9 +208,14 @@ user: action_help: List group api: GET /users/groups arguments: - --fields: - help: fields to fetch - nargs: "+" + -n: + full: --names-only + help: Only list the name of the groups without any additional info + action: store_true + -f: + full: --full + help: List all the info available for each groups + action: store_true ### user_group_create() create: diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 9e90ece2a..f96146ea0 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1374,7 +1374,7 @@ class RestoreManager(): filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass', 'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid'] entries = read_ldif('%s/permission.ldif' % app_settings_in_archive, filtred_entries) - group_list = user_group_list(['cn'])['groups'] + group_list = user_group_list()['groups'] for dn, entry in entries: # Remove the group which has been removed for group in entry['groupPermission']: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 1a5465674..0b77a3e5c 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -169,13 +169,13 @@ def user_permission_update(operation_logger, app=[], permission=None, add_userna # Validate that the group exist for g in add_group: - if g not in user_group_list(['cn'])['groups']: + if g not in user_group_list()['groups']: raise YunohostError('group_unknown', group=g) for u in add_username: if u not in user_list(['uid'])['users']: raise YunohostError('user_unknown', user=u) for g in del_group: - if g not in user_group_list(['cn'])['groups']: + if g not in user_group_list()['groups']: raise YunohostError('group_unknown', group=g) for u in del_username: if u not in user_list(['uid'])['users']: @@ -244,14 +244,12 @@ def user_permission_update(operation_logger, app=[], permission=None, add_userna for a in app: allowed_users = set() disallowed_users = set() - group_list = user_group_list(['member'])['groups'] + group_list = user_group_list()['groups'] for g in add_group: - if 'members' in group_list[g]: - allowed_users.union(group_list[g]['members']) + allowed_users.union(group_list[g]['members']) for g in del_group: - if 'members' in group_list[g]: - disallowed_users.union(group_list[g]['members']) + disallowed_users.union(group_list[g]['members']) allowed_users = ','.join(allowed_users) disallowed_users = ','.join(disallowed_users) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index e5480ca92..787058fbc 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -489,66 +489,44 @@ def user_info(username): # # Group subcategory # -def user_group_list(fields=None): +def user_group_list(names_only=False, full=False): """ List users Keyword argument: - filter -- LDAP filter used to search - offset -- Starting number for user fetching - limit -- Maximum number of user fetched - fields -- fields to fetch - + names-only -- Only list the name of the groups without any additional info + full -- List all the info available for each groups """ - from yunohost.utils.ldap import _get_ldap_interface + + # Fetch relevant informations + + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - group_attr = { - 'cn': 'groupname', - 'member': 'members', - 'permission': 'permission' - } - attrs = ['cn'] - groups = {} - if fields: - keys = group_attr.keys() - for attr in fields: - if attr in keys: - attrs.append(attr) - else: - raise YunohostError('field_invalid', attr) + if names_only: + fields_to_fetch = ["cn"] + elif full: + fields_to_fetch = ["cn", "member", "permission"] else: - attrs = ['cn', 'member'] + fields_to_fetch = ["cn", "member"] - result = ldap.search('ou=groups,dc=yunohost,dc=org', - '(objectclass=groupOfNamesYnh)', - attrs) + groups_infos = ldap.search('ou=groups,dc=yunohost,dc=org', + '(objectclass=groupOfNamesYnh)', + fields_to_fetch) - for group in result: - # The group "admins" should be hidden for the user - if group_attr['cn'] == "admins": - continue - entry = {} - for attr, values in group.items(): - if values: - if attr == "member": - entry[group_attr[attr]] = [] - for v in values: - entry[group_attr[attr]].append(v.split("=")[1].split(",")[0]) - elif attr == "permission": - entry[group_attr[attr]] = {} - for v in values: - permission = v.split("=")[1].split(",")[0].split(".")[1] - pType = v.split("=")[1].split(",")[0].split(".")[0] - if permission in entry[group_attr[attr]]: - entry[group_attr[attr]][permission].append(pType) - else: - entry[group_attr[attr]][permission] = [pType] - else: - entry[group_attr[attr]] = values[0] + # Parse / organize information to be outputed - groupname = entry[group_attr['cn']] - groups[groupname] = entry + groups = {} + for infos in groups_infos: + name = infos["cn"][0] + groups[name] = {} + if "member" in fields_to_fetch: + groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])] + if "permission" in fields_to_fetch: + groups[name]["permissions"] = [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])] + + if names_only: + groups = groups.keys() return {'groups': groups} diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index 186cdbdec..c3b5065a1 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -40,6 +40,20 @@ def _get_ldap_interface(): return _ldap_interface + +# We regularly want to extract stuff like 'bar' in ldap path like +# foo=bar,dn=users.example.org,ou=example.org,dc=org so this small helper allow +# to do this without relying of dozens of mysterious string.split()[0] +# +# e.g. using _ldap_path_extract(path, "foo") on the previous example will +# return bar + +def _ldap_path_extract(path, info): + for element in path.split(","): + if element.startswith(info + "="): + return element[len(info + "="):] + + # Add this to properly close / delete the ldap interface / authenticator # when Python exits ... # Otherwise there's a risk that some funky error appears at the very end From c5d0a270980188a0a2d04f8f6aece63a727d4206 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 03:00:29 +0200 Subject: [PATCH 07/92] Simplify group info and group update interface and code --- data/actionsmap/yunohost.yml | 8 +- .../0011_setup_group_permission.py | 4 +- src/yunohost/tests/test_user-group.py | 20 ++-- src/yunohost/user.py | 101 ++++++++---------- 4 files changed, 58 insertions(+), 75 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b463df646..e727b87ef 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -249,15 +249,15 @@ user: extra: pattern: *pattern_groupname -a: - full: --add-user - help: User to add in group + full: --add + help: User(s) to add in the group nargs: "*" metavar: USERNAME extra: pattern: *pattern_username -r: - full: --remove-user - help: User to remove in group + full: --remove + help: User(s) to remove in the group nargs: "*" metavar: USERNAME extra: diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index d0bae2d95..d2924f0af 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -66,8 +66,8 @@ class MyMigration(Migration): ldap.update('uid=%s,ou=users' % username, {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) user_group_create(username, gid=user_info['uidNumber'][0], sync_perm=False) - user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=False) + user_group_update(groupname=username, add=username, force=True, sync_perm=False) + user_group_update(groupname='all_users', add=username, force=True, sync_perm=False) def migrate_app_permission(self, app=None): diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 1defce466..34e515ea0 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -26,8 +26,8 @@ def setup_function(function): user_group_create("dev") user_group_create("apps") - user_group_update("dev", add_user=["alice"]) - user_group_update("apps", add_user=["bob"]) + user_group_update("dev", add=["alice"]) + user_group_update("apps", add=["bob"]) def teardown_function(function): clean_user_groups() @@ -151,28 +151,28 @@ def test_update_user_1(): assert "NewLast" == info['lastname'] def test_update_group_1(): - user_group_update("dev", add_user=["bob"]) + user_group_update("dev", add=["bob"]) group_res = user_group_list()['groups'] assert set(["alice", "bob"]) == set(group_res['dev']['members']) def test_update_group_2(): # Try to add a user in a group when the user is already in - user_group_update("apps", add_user=["bob"]) + user_group_update("apps", add=["bob"]) group_res = user_group_list()['groups'] assert ["bob"] == group_res['apps']['members'] def test_update_group_3(): # Try to remove a user in a group - user_group_update("apps", remove_user=["bob"]) + user_group_update("apps", remove=["bob"]) group_res = user_group_list()['groups'] assert "members" not in group_res['apps'] def test_update_group_4(): # Try to remove a user in a group when it is not already in - user_group_update("apps", remove_user=["jack"]) + user_group_update("apps", remove=["jack"]) group_res = user_group_list()['groups'] assert ["bob"] == group_res['apps']['members'] @@ -190,21 +190,21 @@ def test_bad_update_user_1(): def bad_update_group_1(): # Check groups not found with pytest.raises(YunohostError): - user_group_update("not_exit", add_user=["alice"]) + user_group_update("not_exit", add=["alice"]) def test_bad_update_group_2(): # Check remove user in groups "all_users" not allowed with pytest.raises(YunohostError): - user_group_update("all_users", remove_user=["alice"]) + user_group_update("all_users", remove=["alice"]) def test_bad_update_group_3(): # Check remove user in it own group not allowed with pytest.raises(YunohostError): - user_group_update("alice", remove_user=["alice"]) + user_group_update("alice", remove=["alice"]) def test_bad_update_group_1(): # Check add bad user in group with pytest.raises(YunohostError): - user_group_update("dev", add_user=["not_exist"]) + user_group_update("dev", add=["not_exist"]) assert "not_exist" not in user_group_list()["groups"]["dev"] diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 787058fbc..a1a671ccf 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -32,6 +32,7 @@ import crypt import random import string import subprocess +import copy from moulinette import m18n from yunohost.utils.error import YunohostError @@ -219,8 +220,8 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, # Create group for user and add to group 'all_users' user_group_create(groupname=username, gid=uid, sync_perm=False) - user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=True) + user_group_update(groupname=username, add=username, force=True, sync_perm=False) + user_group_update(groupname='all_users', add=username, force=True, sync_perm=True) # TODO: Send a welcome mail to user logger.success(m18n.n('user_created')) @@ -610,84 +611,67 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): @is_unit_operation([('groupname', 'user')]) -def user_group_update(operation_logger, groupname, add_user=None, remove_user=None, force=False, sync_perm=True): +def user_group_update(operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True): """ Update user informations Keyword argument: groupname -- Groupname to update - add_user -- User to add in group - remove_user -- User to remove in group + add -- User(s) to add in group + remove -- User(s) to remove in group """ from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface + # FIXME : we should also refuse to edit the main group of a user (e.g. group 'sam' related to user 'sam') + if (groupname == 'all_users' or groupname == 'admins') and not force: raise YunohostError('edit_group_not_allowed', group=groupname) ldap = _get_ldap_interface() - # Populate group informations - attrs_to_fetch = ['member'] - result = ldap.search(base='ou=groups,dc=yunohost,dc=org', - filter='cn=' + groupname, attrs=attrs_to_fetch) - if not result: - raise YunohostError('group_unknown', group=groupname) - group = result[0] + # We extract the uid for each member of the group to keep a simple flat list of members + current_group = user_group_info(groupname)["members"] + new_group = copy.copy(current_group) - new_group_list = {'member': set(), 'memberUid': set()} - if 'member' in group: - new_group_list['member'] = set(group['member']) - else: - group['member'] = [] + existing_users = user_list()['users'].keys() - existing_users = user_list(fields=['uid'])['users'].keys() + if add: + users_to_add = [add] if not isinstance(add, list) else add - if add_user: - if not isinstance(add_user, list): - add_user = [add_user] - - for user in add_user: + for user in users_to_add: if user not in existing_users: raise YunohostError('user_unknown', user=user) - for user in add_user: - userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" - if userDN in group['member']: + if user in current_group: logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) - new_group_list['member'].add(userDN) - if remove_user: - if not isinstance(remove_user, list): - remove_user = [remove_user] + new_group += users_to_add - for user in remove_user: + if remove: + users_to_remove = [remove] if not isinstance(remove, list) else remove + + for user in users_to_remove: if user == groupname: + # FIXME : well if the user equals the group, why pass the two info... + # anyway we should just forbid this from the very beginning ... (editing a user-related group) raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname) - for user in remove_user: - userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" - if 'member' in group and userDN in group['member']: - new_group_list['member'].remove(userDN) - else: + if user not in current_group: logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) - # Sychronise memberUid with member (to keep the posix group structure) - # In posixgroup the main group of each user is only written in the gid number of the user - for member in new_group_list['member']: - member_Uid = member.split("=")[1].split(",")[0] - # Don't add main user in the group. - # Note that in the Unix system the main user of the group is linked by the gid in the user attribute. - # So the main user need to be not in the memberUid list of his own group. - if member_Uid != groupname: - new_group_list['memberUid'].add(member_Uid) + # Remove users_to_remove from new_group + # Kinda like a new_group -= users_to_remove + new_group = [u for u in new_group if u not in users_to_remove] + + new_group_dns = ["uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group] operation_logger.start() - if new_group_list['member'] != set(group['member']): - if not ldap.update('cn=%s,ou=groups' % groupname, new_group_list): + if set(new_group) != set(current_group): + if not ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}): raise YunohostError('group_update_failed', group=groupname) logger.success(m18n.n('group_updated', group=groupname)) @@ -705,26 +689,25 @@ def user_group_info(groupname): """ - from yunohost.utils.ldap import _get_ldap_interface + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - group_attrs = [ - 'cn', 'member', 'permission' - ] - result = ldap.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs) + # Fetch info for this group + result = ldap.search('ou=groups,dc=yunohost,dc=org', + "cn=" + groupname, + ["cn", "member", "permission"]) if not result: raise YunohostError('group_unknown', group=groupname) - group = result[0] + infos = result[0] - result_dict = { - 'groupname': group['cn'][0], - 'member': None + # Format data + + return { + 'members': [_ldap_path_extract(p, "uid") for p in infos.get("member", [])], + 'permissions': [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])] } - if 'member' in group: - result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} - return result_dict # From 97c637f44c4b3d1d2625c1c34bdeece51ace0ea4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 03:00:59 +0200 Subject: [PATCH 08/92] Fix group command descriptions in the actionmap --- data/actionsmap/yunohost.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e727b87ef..d4940c043 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -201,11 +201,11 @@ user: subcategories: group: - subcategory_help: Manage group + subcategory_help: Manage user groups actions: ### user_group_list() list: - action_help: List group + action_help: List existing groups api: GET /users/groups arguments: -n: @@ -223,7 +223,7 @@ user: api: POST /users/groups arguments: groupname: - help: The unique group name to add + help: Name of the group to be created extra: pattern: &pattern_groupname - !!str ^[a-z0-9_]+$ @@ -235,7 +235,7 @@ user: api: DELETE /users/groups/ arguments: groupname: - help: Username to delete + help: Name of the group to be deleted extra: pattern: *pattern_groupname @@ -245,7 +245,7 @@ user: api: PUT /users/groups/ arguments: groupname: - help: Username to update + help: Name of the group to be updated extra: pattern: *pattern_groupname -a: @@ -265,11 +265,11 @@ user: ### user_group_info() info: - action_help: Get group information + action_help: Get information for a specific group api: GET /users/groups/ arguments: groupname: - help: Groupname to get information + help: Name of the group to get info about extra: pattern: *pattern_username From 112976f8ee17e1ebb0c74edb2fbe5c9dc6441fa3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 03:30:51 +0200 Subject: [PATCH 09/92] Refuse to edit user primary groups --- locales/en.json | 5 ++--- src/yunohost/user.py | 43 ++++++++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/locales/en.json b/locales/en.json index 815c40b75..d3abf4fd0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -196,7 +196,6 @@ "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", - "edit_group_not_allowed": "You are not allowed to edit the group {group:s}", "edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.", "error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "executing_command": "Executing command '{command:s}'…", @@ -235,9 +234,10 @@ "group_name_already_exist": "Group {name:s} already exist", "group_created": "Group '{group}' successfully created", "group_creation_failed": "Group creation failed for group '{group}'", + "group_cannot_be_edited": "The group {group:s} cannot be edited manually.", + "group_cannot_be_deleted": "The group {group:s} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Group '{group} 'deletion failed", - "group_deletion_not_allowed": "The group {group:s} cannot be deleted manually.", "group_info_failed": "Group info failed", "group_unknown": "Group {group:s} unknown", "group_updated": "Group '{group}' updated", @@ -449,7 +449,6 @@ "port_unavailable": "Port {port:d} is not available", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create ' or the admin interface.", "remove_main_permission_not_allowed": "Removing the main permission is not allowed", - "remove_user_of_group_not_allowed": "You are not allowed to remove the user {user:s} in the group {group:s}", "regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", "regenconf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but has been kept back.", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a1a671ccf..8427cbd42 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -219,8 +219,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, exc_info=1) # Create group for user and add to group 'all_users' - user_group_create(groupname=username, gid=uid, sync_perm=False) - user_group_update(groupname=username, add=username, force=True, sync_perm=False) + user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False) user_group_update(groupname='all_users', add=username, force=True, sync_perm=True) # TODO: Send a welcome mail to user @@ -533,7 +532,7 @@ def user_group_list(names_only=False, full=False): @is_unit_operation([('groupname', 'user')]) -def user_group_create(operation_logger, groupname, gid=None, sync_perm=True): +def user_group_create(operation_logger, groupname, gid=None, primary_group=False, sync_perm=True): """ Create group @@ -575,6 +574,12 @@ def user_group_create(operation_logger, groupname, gid=None, sync_perm=True): 'gidNumber': gid, } + # Here we handle the creation of a primary group + # We want to initialize this group to contain the corresponding user + # (then we won't be able to add/remove any user in this group) + if primary_group: + attr_dict["member"] = ["uid=" + groupname + ",ou=users,dc=yunohost,dc=org"] + if ldap.add('cn=%s,ou=groups' % groupname, attr_dict): logger.success(m18n.n('group_created', group=groupname)) if sync_perm: @@ -596,9 +601,14 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface - forbidden_groups = ["all_users", "admins"] + user_list(fields=['uid'])['users'].keys() - if not force and groupname in forbidden_groups: - raise YunohostError('group_deletion_not_allowed', group=groupname) + # Refuse to delete primary groups of a user (e.g. group 'sam' related to user 'sam') + # without the force option... + # + # We also can't delete "all_users" because that's a special group... + existing_users = user_list()['users'].keys() + undeletable_groups = existing_users + ["all_users", "admins"] + if groupname in undeletable_groups and not force: + raise YunohostError('group_cannot_be_deleted', group=groupname) operation_logger.start() ldap = _get_ldap_interface() @@ -625,19 +635,18 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface - # FIXME : we should also refuse to edit the main group of a user (e.g. group 'sam' related to user 'sam') - - if (groupname == 'all_users' or groupname == 'admins') and not force: - raise YunohostError('edit_group_not_allowed', group=groupname) - - ldap = _get_ldap_interface() + # Refuse to edit a primary group of a user (e.g. group 'sam' related to user 'sam') + # Those kind of group should only ever contain the user (e.g. sam) and only this one. + # We also can't edit "all_users" without the force option because that's a special group... + existing_users = user_list()['users'].keys() + uneditable_groups = existing_users + ["all_users", "admins"] + if groupname in uneditable_groups and not force: + raise YunohostError('group_cannot_be_edited', group=groupname) # We extract the uid for each member of the group to keep a simple flat list of members current_group = user_group_info(groupname)["members"] new_group = copy.copy(current_group) - existing_users = user_list()['users'].keys() - if add: users_to_add = [add] if not isinstance(add, list) else add @@ -654,11 +663,6 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= users_to_remove = [remove] if not isinstance(remove, list) else remove for user in users_to_remove: - if user == groupname: - # FIXME : well if the user equals the group, why pass the two info... - # anyway we should just forbid this from the very beginning ... (editing a user-related group) - raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname) - if user not in current_group: logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) @@ -671,6 +675,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= operation_logger.start() if set(new_group) != set(current_group): + ldap = _get_ldap_interface() if not ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}): raise YunohostError('group_update_failed', group=groupname) From 6276485665977aae1e6407714988ca354fad5779 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 04:06:12 +0200 Subject: [PATCH 10/92] Simplify permission_list ... it really sounds like we don't need all these options --- data/actionsmap/yunohost.yml | 27 ++---------- locales/en.json | 1 - src/yunohost/backup.py | 6 +-- src/yunohost/permission.py | 79 +++++++----------------------------- src/yunohost/user.py | 6 +-- 5 files changed, 23 insertions(+), 96 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index d4940c043..2bca684cd 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -274,33 +274,12 @@ user: pattern: *pattern_username permission: - subcategory_help: Manage user permission + subcategory_help: Manage permissions actions: ### user_permission_list() list: - action_help: List access to user and group - api: GET /users/permissions/ - arguments: - -a: - full: --app - help: Application to manage the permission - nargs: "*" - metavar: APP - -p: - full: --permission - help: Name of permission (main by default) - nargs: "*" - metavar: PERMISSION - -u: - full: --username - help: Username - nargs: "*" - metavar: USER - -g: - full: --group - help: Group name - nargs: "*" - metavar: GROUP + action_help: List permissions and corresponding accesses + api: GET /users/permissions/ ### user_permission_add() add: diff --git a/locales/en.json b/locales/en.json index d3abf4fd0..b02bf2238 100644 --- a/locales/en.json +++ b/locales/en.json @@ -438,7 +438,6 @@ "permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", "permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed", "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", - "permission_name_not_valid": "Permission name '{permission:s}' not valid", "permission_update_failed": "Permission update failed", "permission_generated": "The permission database has been updated", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index f96146ea0..fdbd8c62c 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1256,10 +1256,8 @@ class RestoreManager(): # Restore permission for the app which is installed for per in old_apps_permission: - try: - permission_name, app_name = per['cn'][0].split('.') - except: - logger.warning(m18n.n('permission_name_not_valid', permission=per['cn'][0])) + # FIXME : will come here later to fix this following previous commits ... + permission_name, app_name = per['cn'][0].split('.') if _is_installed(app_name): if not ldap.add('cn=%s,ou=permission' % per['cn'][0], per): raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 0b77a3e5c..fbb43e8b3 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -42,79 +42,30 @@ logger = getActionLogger('yunohost.user') # -def user_permission_list(app=None, permission=None, username=None, group=None): +def user_permission_list(): """ - List permission for specific application - - Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - username -- Username to get informations - group -- Groupname to get informations + List permissions and corresponding accesses """ - from yunohost.utils.ldap import _get_ldap_interface + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract + + # Fetch all permissions objects ldap = _get_ldap_interface() - - permission_attrs = [ - 'cn', - 'groupPermission', - 'inheritPermission', - 'URL', - ] - - # Normally app is alway defined but it should be possible to set it - if app and not isinstance(app, list): - app = [app] - if permission and not isinstance(permission, list): - permission = [permission] - if not isinstance(username, list): - username = [username] - if not isinstance(group, list): - group = [group] + permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', + ['cn', 'groupPermission', 'inheritPermission', 'URL']) permissions = {} + for infos in permissions_infos: - result = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', permission_attrs) + name = infos['cn'][0] - for res in result: - try: - permission_name, app_name = res['cn'][0].split('.') - except: - logger.warning(m18n.n('permission_name_not_valid', permission=res['cn'][0])) - group_name = [] - if 'groupPermission' in res: - for g in res['groupPermission']: - group_name.append(g.split("=")[1].split(",")[0]) - user_name = [] - if 'inheritPermission' in res: - for u in res['inheritPermission']: - user_name.append(u.split("=")[1].split(",")[0]) - - # Don't show the result if the user defined a specific permission, user or group - if app and app_name not in app: - continue - if permission and permission_name not in permission: - continue - if username[0] and not set(username) & set(user_name): - continue - if group[0] and not set(group) & set(group_name): - continue - - if app_name not in permissions: - permissions[app_name] = {} - - permissions[app_name][permission_name] = {'allowed_users': [], 'allowed_groups': []} - for g in group_name: - permissions[app_name][permission_name]['allowed_groups'].append(g) - for u in user_name: - permissions[app_name][permission_name]['allowed_users'].append(u) - if 'URL' in res: - permissions[app_name][permission_name]['URL'] = [] - for u in res['URL']: - permissions[app_name][permission_name]['URL'].append(u) + permissions[name] = { + "allowed_users": [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])], + "allowed_groups": [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])], + "urls": infos.get("URL", []) + } return {'permissions': permissions} diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 8427cbd42..3eb329f4e 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -453,7 +453,7 @@ def user_info(username): if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) - elif not user_permission_list(app="mail", permission="main", username=username)['permissions']: + elif username not in user_permission_list()["permissions"]["mail.main"]["allowed_users"]: logger.warning(m18n.n('mailbox_disabled', user=username)) else: cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] @@ -719,9 +719,9 @@ def user_group_info(groupname): # Permission subcategory # -def user_permission_list(app=None, permission=None, username=None, group=None, sync_perm=True): +def user_permission_list(): import yunohost.permission - return yunohost.permission.user_permission_list(app, permission, username, group) + return yunohost.permission.user_permission_list() @is_unit_operation([('app', 'user')]) From 41e6f1b81cdf032949f960fb7095a697d4af4256 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 14:37:15 +0200 Subject: [PATCH 11/92] Simplify permission_add/remove to just permission_update with --add and --remove, similar to what's done for groups --- data/actionsmap/yunohost.yml | 63 +++--------- locales/en.json | 6 +- src/yunohost/permission.py | 180 ++++++++++++----------------------- src/yunohost/user.py | 18 +--- 4 files changed, 84 insertions(+), 183 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2bca684cd..ebdd2b982 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -276,64 +276,31 @@ user: permission: subcategory_help: Manage permissions actions: + ### user_permission_list() list: action_help: List permissions and corresponding accesses api: GET /users/permissions/ - ### user_permission_add() - add: - action_help: Grant access right to users and group - api: POST /users/permissions/ + ### user_permission_update() + update: + action_help: Grant / remove permissions to groups or users + api: POST /users/permissions/ arguments: - app: - help: Application to manage the permission - nargs: "+" - -p: - full: --permission - help: Name of permission (main by default) + permission: + help: Permission to manage (e.g. mail.main or wordpress.editors) + -a: + full: --add + help: Group or user names to add to this permission nargs: "*" - metavar: PERMISSION - -u: - full: --username - help: Username - nargs: "*" - metavar: USER + metavar: GROUP_OR_USER extra: pattern: *pattern_username - -g: - full: --group - help: Group name + -r: + full: --remove + help: Group or user names to remove from this permission nargs: "*" - metavar: GROUP - extra: - pattern: *pattern_username - - ### user_permission_remove() - remove: - action_help: Revoke access right to users and group - api: PUT /users/permissions/ - arguments: - app: - help: Application to manage the permission - nargs: "+" - -p: - full: --permission - help: Name of permission (main by default) - nargs: "*" - metavar: PERMISSION - -u: - full: --username - help: Username - nargs: "*" - metavar: USER - extra: - pattern: *pattern_username - -g: - full: --group - help: Group name - nargs: "*" - metavar: GROUP + metavar: GROUP_OR_USER extra: pattern: *pattern_username diff --git a/locales/en.json b/locales/en.json index b02bf2238..725bb1f8c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -196,7 +196,6 @@ "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", - "edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.", "error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", @@ -229,8 +228,8 @@ "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", - "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled for app '{app:s}'", - "group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' disabled for app '{app:s}'", + "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled'", + "group_already_disallowed": "Group '{group:s}' already has permission '{permission:s}' disabled'", "group_name_already_exist": "Group {name:s} already exist", "group_created": "Group '{group}' successfully created", "group_creation_failed": "Group creation failed for group '{group}'", @@ -397,7 +396,6 @@ "mysql_db_creation_failed": "MySQL database creation failed", "mysql_db_init_failed": "MySQL database init failed", "mysql_db_initialized": "The MySQL database has been initialized", - "need_define_permission_before": "You need to redefine the permission using 'yunohost user permission add -u USER' before removing an allowed group", "network_check_mx_ko": "DNS MX record is not set", "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index fbb43e8b3..20c34ada8 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -24,6 +24,7 @@ Manage permissions """ +import copy import grp import random @@ -45,7 +46,6 @@ logger = getActionLogger('yunohost.user') def user_permission_list(): """ List permissions and corresponding accesses - """ from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract @@ -70,146 +70,92 @@ def user_permission_list(): return {'permissions': permissions} -def user_permission_update(operation_logger, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None, sync_perm=True): +def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - add_username -- Username to allow - add_group -- Groupname to allow - del_username -- Username to disallow - del_group -- Groupname to disallow - + permission -- Name of the permission (e.g. mail.mail or wordpress.editors) + add -- List of groups or usernames to add to this permission + remove -- List of groups or usernames to remove from to this permission """ from yunohost.hook import hook_callback from yunohost.user import user_group_list - from yunohost.utils.ldap import _get_ldap_interface + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - if permission: - if not isinstance(permission, list): - permission = [permission] - else: - permission = ["main"] + # Fetch currently allowed groups for this permission - if add_group: - if not isinstance(add_group, list): - add_group = [add_group] - else: - add_group = [] - - if add_username: - if not isinstance(add_username, list): - add_username = [add_username] - else: - add_username = [] - - if del_group: - if not isinstance(del_group, list): - del_group = [del_group] - else: - del_group = [] - - if del_username: - if not isinstance(del_username, list): - del_username = [del_username] - else: - del_username = [] - - # Validate that the group exist - for g in add_group: - if g not in user_group_list()['groups']: - raise YunohostError('group_unknown', group=g) - for u in add_username: - if u not in user_list(['uid'])['users']: - raise YunohostError('user_unknown', user=u) - for g in del_group: - if g not in user_group_list()['groups']: - raise YunohostError('group_unknown', group=g) - for u in del_username: - if u not in user_list(['uid'])['users']: - raise YunohostError('user_unknown', user=u) - - # Merge user and group (note that we consider all user as a group) - add_group.extend(add_username) - del_group.extend(del_username) - - if 'all_users' in add_group or 'all_users' in del_group: - raise YunohostError('edit_permission_with_group_all_users_not_allowed') - - # Populate permission informations - permission_attrs = [ - 'cn', - 'groupPermission', - ] result = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', permission_attrs) + '(objectclass=permissionYnh)', + ["cn", "groupPermission"]) result = {p['cn'][0]: p for p in result} + if permission not in result: + raise YunohostError('permission_not_found', permission=permission) - new_per_dict = {} + current_allowed_groups = [_ldap_path_extract(p, "cn") for p in result[permission].get("groupPermission", [])] - for a in app: - for per in permission: - permission_name = per + '.' + a - if permission_name not in result: - raise YunohostError('permission_not_found', permission=per, app=a) - new_per_dict[permission_name] = set() - if 'groupPermission' in result[permission_name]: - new_per_dict[permission_name] = set(result[permission_name]['groupPermission']) + # Compute new allowed group list (and make sure what we're doing make sense) - for g in del_group: - if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: - raise YunohostError('need_define_permission_before') - group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' - if group_name not in new_per_dict[permission_name]: - logger.warning(m18n.n('group_already_disallowed', permission=per, app=a, group=g)) - else: - new_per_dict[permission_name].remove(group_name) + new_allowed_groups = copy.copy(current_allowed_groups) - if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: - new_per_dict[permission_name].remove('cn=all_users,ou=groups,dc=yunohost,dc=org') - for g in add_group: - group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' - if group_name in new_per_dict[permission_name]: - logger.warning(m18n.n('group_already_allowed', permission=per, app=a, group=g)) - else: - new_per_dict[permission_name].add(group_name) + if add: + existing_groups = user_group_list()['groups'].keys() + groups_to_add = [add] if not isinstance(add, list) else add + for group in groups_to_add: + if group not in existing_groups: + raise YunohostError('group_unknown', group=group) + if group in current_allowed_groups: + logger.warning(m18n.n('group_already_allowed', permission=permission, group=group)) + new_allowed_groups += groups_to_add + + if remove: + groups_to_remove = [remove] if not isinstance(remove, list) else remove + for group in groups_to_remove: + if group not in existing_groups: + raise YunohostError('group_unknown', group=group) + if group not in current_allowed_groups: + logger.warning(m18n.n('group_already_disallowed', permission=permission, group=group)) + + new_allowed_groups = [g for g in new_allowed_groups if g not in groups_to_remove] + + # If we end up with something like allowed groups is ["all_users", "volunteers"] + # we shall warn the users that they should probably choose between one or the other, + # because the current situation is probably not what they expect / is temporary ? + + if len(new_allowed_groups) > 1 and "all_users" in new_allowed_groups: + # FIXME : write a better explanation + logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the specific groups currently allowed.") + + # Commit the new allowed group list operation_logger.start() - for per, val in new_per_dict.items(): - # Don't update LDAP if we update exactly the same values - if val == set(result[per]['groupPermission'] if 'groupPermission' in result[per] else []): - continue - if ldap.update('cn=%s,ou=permission' % per, {'groupPermission': val}): - p = per.split('.') - logger.debug(m18n.n('permission_updated', permission=p[0], app=p[1])) - else: - raise YunohostError('permission_update_failed') + # Don't update LDAP if we update exactly the same values + if set(new_allowed_groups) == set(current_allowed_groups): + logger.warning("No change was applied because not relevant modification were found") + elif ldap.update('cn=%s,ou=permission' % permission, + {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}): + logger.debug(m18n.n('permission_updated', permission=permission)) - if sync_perm: - permission_sync_to_user() + # Trigger permission sync if asked - for a in app: - allowed_users = set() - disallowed_users = set() - group_list = user_group_list()['groups'] + if sync_perm: + permission_sync_to_user() - for g in add_group: - allowed_users.union(group_list[g]['members']) - for g in del_group: - disallowed_users.union(group_list[g]['members']) + # Trigger app callbacks - allowed_users = ','.join(allowed_users) - disallowed_users = ','.join(disallowed_users) - if add_group: - hook_callback('post_app_addaccess', args=[app, allowed_users]) - if del_group: - hook_callback('post_app_removeaccess', args=[app, disallowed_users]) + # FIXME FIXME FIXME - return user_permission_list(app, permission) + #if groups_to_add: + # hook_callback('post_app_addaccess', args=[app, allowed_users]) + #if groups_to_remove: + # hook_callback('post_app_removeaccess', args=[app, disallowed_users]) + + else: + raise YunohostError('permission_update_failed') + + return user_permission_list()["permissions"][permission] def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True): diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 3eb329f4e..92bdcf7a4 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -724,21 +724,11 @@ def user_permission_list(): return yunohost.permission.user_permission_list() -@is_unit_operation([('app', 'user')]) -def user_permission_add(operation_logger, app, permission="main", username=None, group=None, sync_perm=True): +@is_unit_operation([('permission', 'user')]) +def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): import yunohost.permission - return yunohost.permission.user_permission_update(operation_logger, app, permission=permission, - add_username=username, add_group=group, - del_username=None, del_group=None, - sync_perm=sync_perm) - - -@is_unit_operation([('app', 'user')]) -def user_permission_remove(operation_logger, app, permission="main", username=None, group=None, sync_perm=True): - import yunohost.permission - return yunohost.permission.user_permission_update(operation_logger, app, permission=permission, - add_username=None, add_group=None, - del_username=username, del_group=group, + return yunohost.permission.user_permission_update(operation_logger, permission, + add=add, remove=remove, sync_perm=sync_perm) From 45483f4116e487be9d344fadff222007087a28e7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 15:16:09 +0200 Subject: [PATCH 12/92] --short and --full options for group_list and permission_list --- data/actionsmap/yunohost.yml | 18 ++++++++++++++---- src/yunohost/permission.py | 24 +++++++++++++++--------- src/yunohost/user.py | 28 +++++++++++----------------- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index ebdd2b982..ece642d0d 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -208,13 +208,13 @@ user: action_help: List existing groups api: GET /users/groups arguments: - -n: - full: --names-only - help: Only list the name of the groups without any additional info + -s: + full: --short + help: List only the names of groups action: store_true -f: full: --full - help: List all the info available for each groups + help: Display all informations known about each groups action: store_true ### user_group_create() @@ -281,6 +281,16 @@ user: list: action_help: List permissions and corresponding accesses api: GET /users/permissions/ + arguments: + -s: + full: --short + help: List only the names of permissions + action: store_true + -f: + full: --full + help: Display all informations known about each permissions, including the full list of users corresponding to allowed groups. + action: store_true + ### user_permission_update() update: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 20c34ada8..ab79ff7ed 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -43,29 +43,35 @@ logger = getActionLogger('yunohost.user') # -def user_permission_list(): +def user_permission_list(short=False, full=False): """ List permissions and corresponding accesses """ - from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract + # Fetch relevant informations - # Fetch all permissions objects + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org', '(objectclass=permissionYnh)', - ['cn', 'groupPermission', 'inheritPermission', 'URL']) + ["cn", 'groupPermission', 'inheritPermission', 'URL']) + + # Parse / organize information to be outputed permissions = {} for infos in permissions_infos: name = infos['cn'][0] + permissions[name] = {} - permissions[name] = { - "allowed_users": [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])], - "allowed_groups": [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])], - "urls": infos.get("URL", []) - } + permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])] + + if full: + permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])], + permissions[name]["urls"] = infos.get("URL", []) + + if short: + permissions = permissions.keys() return {'permissions': permissions} diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 92bdcf7a4..80f558809 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -489,12 +489,12 @@ def user_info(username): # # Group subcategory # -def user_group_list(names_only=False, full=False): +def user_group_list(short=False, full=False): """ List users Keyword argument: - names-only -- Only list the name of the groups without any additional info + short -- Only list the name of the groups without any additional info full -- List all the info available for each groups """ @@ -502,30 +502,24 @@ def user_group_list(names_only=False, full=False): from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - - if names_only: - fields_to_fetch = ["cn"] - elif full: - fields_to_fetch = ["cn", "member", "permission"] - else: - fields_to_fetch = ["cn", "member"] - groups_infos = ldap.search('ou=groups,dc=yunohost,dc=org', '(objectclass=groupOfNamesYnh)', - fields_to_fetch) + ["cn", "member", "permission"]) # Parse / organize information to be outputed groups = {} for infos in groups_infos: + name = infos["cn"][0] groups[name] = {} - if "member" in fields_to_fetch: - groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])] - if "permission" in fields_to_fetch: + + groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])] + + if full: groups[name]["permissions"] = [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])] - if names_only: + if short: groups = groups.keys() return {'groups': groups} @@ -719,9 +713,9 @@ def user_group_info(groupname): # Permission subcategory # -def user_permission_list(): +def user_permission_list(short=False, full=False): import yunohost.permission - return yunohost.permission.user_permission_list() + return yunohost.permission.user_permission_list(short, full) @is_unit_operation([('permission', 'user')]) From e5676c4b30db38f63ec740ac987aa697f473173a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 15:16:26 +0200 Subject: [PATCH 13/92] Propagate change in permission_list to permission_update --- src/yunohost/permission.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index ab79ff7ed..3a6bde077 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -92,33 +92,31 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Fetch currently allowed groups for this permission - result = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', - ["cn", "groupPermission"]) - result = {p['cn'][0]: p for p in result} - if permission not in result: + permissions = user_permission_list(full=True)["permissions"] + if permission not in permissions: raise YunohostError('permission_not_found', permission=permission) - current_allowed_groups = [_ldap_path_extract(p, "cn") for p in result[permission].get("groupPermission", [])] + current_allowed_groups = permissions[permission]["allowed"] + all_existing_groups = user_group_list()['groups'].keys() # Compute new allowed group list (and make sure what we're doing make sense) new_allowed_groups = copy.copy(current_allowed_groups) if add: - existing_groups = user_group_list()['groups'].keys() groups_to_add = [add] if not isinstance(add, list) else add for group in groups_to_add: - if group not in existing_groups: + if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: logger.warning(m18n.n('group_already_allowed', permission=permission, group=group)) + new_allowed_groups += groups_to_add if remove: groups_to_remove = [remove] if not isinstance(remove, list) else remove for group in groups_to_remove: - if group not in existing_groups: + if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) if group not in current_allowed_groups: logger.warning(m18n.n('group_already_disallowed', permission=permission, group=group)) @@ -130,7 +128,8 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # because the current situation is probably not what they expect / is temporary ? if len(new_allowed_groups) > 1 and "all_users" in new_allowed_groups: - # FIXME : write a better explanation + # FIXME : i18n + # FIXME : write a better explanation ? logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the specific groups currently allowed.") # Commit the new allowed group list @@ -139,6 +138,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): + # FIXME : i18n logger.warning("No change was applied because not relevant modification were found") elif ldap.update('cn=%s,ou=permission' % permission, {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}): @@ -149,20 +149,20 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if sync_perm: permission_sync_to_user() + new_permission = user_permission_list(full=True)["permissions"][permission] + # Trigger app callbacks + app = permission.split(".")[0] + if add: + hook_callback('post_app_addaccess', args=[app, new_permission["corresponding_users"]]) + if remove: + hook_callback('post_app_removeaccess', args=[app, new_permission["corresponding_users"]]) - # FIXME FIXME FIXME - - #if groups_to_add: - # hook_callback('post_app_addaccess', args=[app, allowed_users]) - #if groups_to_remove: - # hook_callback('post_app_removeaccess', args=[app, disallowed_users]) + return new_permission else: raise YunohostError('permission_update_failed') - return user_permission_list()["permissions"][permission] - def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True): """ From a1d3376613993a8420373eec1b05bddc08a7720f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 15:49:07 +0200 Subject: [PATCH 14/92] Simplify permission_clear, now named permission_reset --- data/actionsmap/yunohost.yml | 16 +++----- locales/en.json | 1 - src/yunohost/permission.py | 75 +++++++++++++----------------------- src/yunohost/user.py | 4 +- 4 files changed, 34 insertions(+), 62 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index ece642d0d..49dde373b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -314,19 +314,13 @@ user: extra: pattern: *pattern_username - ## user_permission_clear() - clear: - action_help: Reset access rights for the app + ## user_permission_reset() + reset: + action_help: Reset allowed groups to the default (all_users) for a given permission api: DELETE /users/permissions/ arguments: - app: - help: Application to manage the permission - nargs: "+" - -p: - full: --permission - help: Name of permission (main by default) - nargs: "*" - metavar: PERMISSION + permission: + help: Permission to be resetted (e.g. mail.main or wordpress.editors) ssh: subcategory_help: Manage ssh access diff --git a/locales/en.json b/locales/en.json index 725bb1f8c..ebbb89fa8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -429,7 +429,6 @@ "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords should not contain the following characters: {forbidden_chars}", - "permission_already_clear": "Permission '{permission:s}' already clear for app {app:s}", "permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", "permission_created": "Permission '{permission:s}' for app {app:s} created", "permission_creation_failed": "Permission creation failed", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 3a6bde077..8816ad950 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -152,11 +152,13 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, new_permission = user_permission_list(full=True)["permissions"][permission] # Trigger app callbacks - app = permission.split(".")[0] - if add: - hook_callback('post_app_addaccess', args=[app, new_permission["corresponding_users"]]) - if remove: - hook_callback('post_app_removeaccess', args=[app, new_permission["corresponding_users"]]) + # FIXME : this is not how this hook works... gotta compute the list of user actually added / removed + + #app = permission.split(".")[0] + #if add: + # hook_callback('post_app_addaccess', args=[app, new_permission["corresponding_users"]]) + #if remove: + # hook_callback('post_app_removeaccess', args=[app, new_permission["corresponding_users"]]) return new_permission @@ -164,63 +166,40 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('permission_update_failed') -def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True): +def user_permission_reset(operation_logger, permission, sync_perm=True): """ - Reset the permission for a specific application + Reset a given permission to just 'all_users' Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - username -- Username to get informations (all by default) - group -- Groupname to get informations (all by default) - + permission -- The name of the permission to be reseted """ from yunohost.hook import hook_callback from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - if permission: - if not isinstance(permission, list): - permission = [permission] - else: - permission = ["main"] + # Fetch existing permission + + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if existing_permission is None: + raise YunohostError('permission_not_found', permission=permission) + + # Update permission with default (all_users) default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} + if ldap.update('cn=%s,ou=permission' % permission, default_permission): + logger.debug(m18n.n('permission_updated', permission=permission)) + else: + raise YunohostError('permission_update_failed') - # Populate permission informations - permission_attrs = [ - 'cn', - 'groupPermission', - ] - result = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', permission_attrs) - result = {p['cn'][0]: p for p in result} + if sync_perm: + permission_sync_to_user() - for a in app: - for per in permission: - permission_name = per + '.' + a - if permission_name not in result: - raise YunohostError('permission_not_found', permission=per, app=a) - if 'groupPermission' in result[permission_name] and 'cn=all_users,ou=groups,dc=yunohost,dc=org' in result[permission_name]['groupPermission']: - logger.warning(m18n.n('permission_already_clear', permission=per, app=a)) - continue - if ldap.update('cn=%s,ou=permission' % permission_name, default_permission): - logger.debug(m18n.n('permission_updated', permission=per, app=a)) - else: - raise YunohostError('permission_update_failed') + new_permission = user_permission_list(full=True)["permissions"][permission] - permission_sync_to_user() + # FIXME : trigger app callbacks + # app = permission.split(".")[0] - for a in app: - permission_name = 'main.' + a - result = ldap.search('ou=permission,dc=yunohost,dc=org', - filter='cn=' + permission_name, attrs=['inheritPermission']) - if result: - allowed_users = result[0]['inheritPermission'] - new_user_list = ','.join(allowed_users) - hook_callback('post_app_removeaccess', args=[app, new_user_list]) - - return user_permission_list(app, permission) + return new_permission # # diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 80f558809..2bf36cfd6 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -727,9 +727,9 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, @is_unit_operation([('app', 'user')]) -def user_permission_clear(operation_logger, app, permission=None, sync_perm=True): +def user_permission_reset(operation_logger, permission, sync_perm=True): import yunohost.permission - return yunohost.permission.user_permission_clear(operation_logger, app, permission, + return yunohost.permission.user_permission_reset(operation_logger, permission, sync_perm=sync_perm) From 3535cb655f17be09ede5157473b28cdc09060b2b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 16:36:17 +0200 Subject: [PATCH 15/92] Fix call of app add/remove access hooks --- src/yunohost/permission.py | 42 +++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 8816ad950..fbfaa43a2 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -67,7 +67,7 @@ def user_permission_list(short=False, full=False): permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])] if full: - permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])], + permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])] permissions[name]["urls"] = infos.get("URL", []) if short: @@ -92,11 +92,11 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Fetch currently allowed groups for this permission - permissions = user_permission_list(full=True)["permissions"] - if permission not in permissions: + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if existing_permission is None: raise YunohostError('permission_not_found', permission=permission) - current_allowed_groups = permissions[permission]["allowed"] + current_allowed_groups = existing_permission["allowed"] all_existing_groups = user_group_list()['groups'].keys() # Compute new allowed group list (and make sure what we're doing make sense) @@ -152,13 +152,19 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, new_permission = user_permission_list(full=True)["permissions"][permission] # Trigger app callbacks - # FIXME : this is not how this hook works... gotta compute the list of user actually added / removed - #app = permission.split(".")[0] - #if add: - # hook_callback('post_app_addaccess', args=[app, new_permission["corresponding_users"]]) - #if remove: - # hook_callback('post_app_removeaccess', args=[app, new_permission["corresponding_users"]]) + app = permission.split(".")[0] + + old_allowed_users = set(existing_permission["corresponding_users"]) + new_allowed_users = set(new_permission["corresponding_users"]) + + effectively_added_users = new_allowed_users - old_allowed_users + effectively_removed_users = old_allowed_users - new_allowed_users + + if effectively_added_users: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + if effectively_removed_users: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) return new_permission @@ -196,8 +202,20 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): new_permission = user_permission_list(full=True)["permissions"][permission] - # FIXME : trigger app callbacks - # app = permission.split(".")[0] + # Trigger app callbacks + + app = permission.split(".")[0] + + old_allowed_users = set(existing_permission["corresponding_users"]) + new_allowed_users = set(new_permission["corresponding_users"]) + + effectively_added_users = new_allowed_users - old_allowed_users + effectively_removed_users = old_allowed_users - new_allowed_users + + if effectively_added_users: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + if effectively_removed_users: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) return new_permission From 574e9aea44a8e25078b506240bc7cedf155dd8e0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 18:13:30 +0200 Subject: [PATCH 16/92] Simplify permission_create/urls/delete interface and code --- data/helpers.d/setting | 57 +++++++-------- src/yunohost/permission.py | 138 ++++++++++++++++++------------------- 2 files changed, 93 insertions(+), 102 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index e6311fc1f..0e432d916 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -230,70 +230,63 @@ ynh_webpath_register () { # Create a new permission for the app # -# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--urls "url" ["url" ...]] -# | arg: app - the application id +# usage: ynh_permission_create --permission "permission" [--urls "url" ["url" ...]] # | arg: permission - the name for the permission (by default a permission named "main" already exist) -# | arg: defaultdisallow - define if all user will be allowed by default -# | arg: urls - the list of urls for the the permission +# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# +# example: ynh_permission_create --permission admin --urls domain.tld/blog/admin ynh_permission_create() { - declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=urls= ) - local app + declare -Ar args_array=( [p]=permission= [u]=urls= ) local permission - local defaultdisallow local urls ynh_handle_getopts_args "$@" - if [[ -n ${defaultdisallow:-} ]]; then - defaultdisallow=",default_allow=False" - fi if [[ -n ${urls:-} ]]; then urls=",urls=['${urls//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' ${urls:-}, sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) # -# usage: ynh_permission_remove --app "app" --permission "permission" -# | arg: app - the application id +# usage: ynh_permission_remove --permission "permission" # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -ynh_permission_remove() { - declare -Ar args_array=( [a]=app= [p]=permission= ) - local app +# +# example: ynh_permission_delete --permission editors +ynh_permission_delete() { + declare -Ar args_array=( [p]=permission= ) local permission ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app', '$permission', sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)" } # Add a path managed by the SSO # -# usage: ynh_permission_add_url --app "app" --permission "permission" --url "url" ["url" ...] -# | arg: app - the application id -# | arg: permission - the name for the permission -# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) +# usage: ynh_permission_add_url --permission "permission" --url "url" ["url" ...] +# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# ynh_permission_add_url() { - declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) - local app + declare -Ar args_array=([p]=permission= [u]=urls= ) local permission - local url + local urls ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission', add=['${urls//';'/"','"}'], sync_perm=False)" } # Remove a path managed by the SSO # # usage: ynh_permission_del_path --app "app" --permission "permission" --url "url" ["url" ...] -# | arg: app - the application id -# | arg: permission - the name for the permission -# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) +# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# ynh_permission_remove_url() { - declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) - local app + declare -Ar args_array=([p]=permission= [u]=urls= ) local permission - local url + local urls ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission', remove=['${url//';'/"','"}'], sync_perm=False)" } diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index fbfaa43a2..960c0853c 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -229,27 +229,22 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): @is_unit_operation(['permission', 'app']) -def permission_create(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): +def permission_create(operation_logger, permission, urls=None, sync_perm=True): """ Create a new permission for a specific application Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) + permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) urls -- list of urls to specify for the permission - """ - from yunohost.domain import _normalize_domain_path + from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() # Validate uniqueness of permission in LDAP - permission_name = str(permission + '.' + app) # str(...) Fix encoding issue - conflict = ldap.get_conflict({ - 'cn': permission_name - }, base_dn='ou=permission,dc=yunohost,dc=org') - if conflict: - raise YunohostError('permission_already_exist', permission=permission, app=app) + if ldap.get_conflict({'cn': permission}, + base_dn='ou=permission,dc=yunohost,dc=org'): + raise YunohostError('permission_already_exist', permission=permission) # Get random GID all_gid = {x.gr_gid for x in grp.getgrall()} @@ -261,110 +256,106 @@ def permission_create(operation_logger, app, permission, urls=None, default_allo attr_dict = { 'objectClass': ['top', 'permissionYnh', 'posixGroup'], - 'cn': permission_name, + 'cn': permission, 'gidNumber': gid, } - if default_allow: + + # For main permission, we add all users by default + if permission.endswith(".main"): attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' if urls: - attr_dict['URL'] = [] - for url in urls: - domain = url[:url.index('/')] - path = url[url.index('/'):] - domain, path = _normalize_domain_path(domain, path) - attr_dict['URL'].append(domain + path) + attr_dict['URL'] = [_normalize_url(url) for url in urls] operation_logger.start() - if ldap.add('cn=%s,ou=permission' % permission_name, attr_dict): + if ldap.add('cn=%s,ou=permission' % permission, attr_dict): if sync_perm: permission_sync_to_user() - logger.debug(m18n.n('permission_created', permission=permission, app=app)) - return user_permission_list(app, permission) - - raise YunohostError('permission_creation_failed') + logger.debug(m18n.n('permission_created', permission=permission)) + return user_permission_list(full=True)["permissions"][permission] + else: + raise YunohostError('permission_creation_failed') @is_unit_operation(['permission', 'app']) -def permission_urls(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True): +def permission_urls(operation_logger, permission, add=None, remove=None, sync_perm=True): """ Update urls related to a permission for a specific application Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - add_url -- Add a new url for a permission - remove_url -- Remove a url for a permission + permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) + add -- List of urls to add + remove -- List of urls to remove """ - from yunohost.domain import _normalize_domain_path from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + # Fetch existing permission - # Populate permission informations - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='cn=' + permission_name, attrs=['URL']) - if not result: - raise YunohostError('permission_not_found', permission=permission, app=app) - permission_obj = result[0] + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if not existing_permission: + raise YunohostError('permission_not_found', permission=permission) - if 'URL' not in permission_obj: - permission_obj['URL'] = [] + # Compute new url list - url = set(permission_obj['URL']) + new_urls = copy.copy(existing_permission["urls"]) - if add_url: - for u in add_url: - domain = u[:u.index('/')] - path = u[u.index('/'):] - domain, path = _normalize_domain_path(domain, path) - url.add(domain + path) - if remove_url: - for u in remove_url: - domain = u[:u.index('/')] - path = u[u.index('/'):] - domain, path = _normalize_domain_path(domain, path) - url.discard(domain + path) + if add: + urls_to_add = [add] if not isinstance(add, list) else add + urls_to_add = [_normalize_url(url) for url in urls_to_add] + new_urls += urls_to_add + if remove: + urls_to_remove = [remove] if not isinstance(remove, list) else remove + urls_to_remove = [_normalize_url(url) for url in urls_to_remove] + new_urls = [u for u in new_urls if u not in urls_to_remove] - if url == set(permission_obj['URL']): + if set(new_urls) == set(existing_permission["urls"]): logger.warning(m18n.n('permission_update_nothing_to_do')) - return user_permission_list(app, permission) + return existing_permission + + # Actually commit the change operation_logger.start() - if ldap.update('cn=%s,ou=permission' % permission_name, {'cn': permission_name, 'URL': url}): + if ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}): if sync_perm: permission_sync_to_user() - logger.debug(m18n.n('permission_updated', permission=permission, app=app)) - return user_permission_list(app, permission) - - raise YunohostError('premission_update_failed') + logger.debug(m18n.n('permission_updated', permission=permission)) + return user_permission_list(full=True)["permissions"][permission] + else: + raise YunohostError('premission_update_failed') @is_unit_operation(['permission', 'app']) -def permission_delete(operation_logger, app, permission, force=False, sync_perm=True): +def permission_delete(operation_logger, permission, force=False, sync_perm=True): """ - Remove a permission for a specific application + Delete a permission Keyword argument: - app -- an application OR sftp, xmpp (metronome), mail - permission -- name of the permission ("main" by default) - + permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) """ - if permission == "main" and not force: + if permission.endswith("main") and not force: raise YunohostError('remove_main_permission_not_allowed') from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # Make sure this permission exists + + existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) + if not existing_permission: + raise YunohostError('permission_not_found', permission=permission) + + # Actually delete the permission + operation_logger.start() - if not ldap.remove('cn=%s,ou=permission' % str(permission + '.' + app)): - raise YunohostError('permission_deletion_failed', permission=permission, app=app) - if sync_perm: - permission_sync_to_user() - logger.debug(m18n.n('permission_deleted', permission=permission, app=app)) + if ldap.remove('cn=%s,ou=permission' % permission): + if sync_perm: + permission_sync_to_user() + logger.debug(m18n.n('permission_deleted', permission=permission)) + else: + raise YunohostError('permission_deletion_failed', permission=permission) def permission_sync_to_user(force=False): @@ -438,3 +429,10 @@ def permission_sync_to_user(force=False): # Reload unscd, otherwise the group ain't propagated to the LDAP database os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') + +def _normalize_url(url): + from yunohost.domain import _normalize_domain_path + domain = url[:url.index('/')] + path = url[url.index('/'):] + domain, path = _normalize_domain_path(domain, path) + return domain + path From 853c6a161a07ca2a935b1eeb3424094f1f8e6a7c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 20:56:56 +0200 Subject: [PATCH 17/92] Simplify permission_sync_to_user ... force is never set to True so I dropped it... --- src/yunohost/permission.py | 71 +++++++++++--------------------------- 1 file changed, 21 insertions(+), 50 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 960c0853c..54aa25e23 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -358,70 +358,41 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) raise YunohostError('permission_deletion_failed', permission=permission) -def permission_sync_to_user(force=False): +def permission_sync_to_user(): """ Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link - - Keyword argument: - force -- Force to recreate all attributes. Used generally with the - backup which uses "slapadd" which doesnt' use the memberOf overlay. - Note that by removing all value and adding a new time, we force the - overlay to update all attributes """ - # Note that a LDAP operation with the same value that is in LDAP crash SLAP. - # So we need to check before each ldap operation that we really change something in LDAP import os from yunohost.app import app_ssowatconf + from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - permission_attrs = [ - 'cn', - 'member', - ] - group_info = ldap.search('ou=groups,dc=yunohost,dc=org', - '(objectclass=groupOfNamesYnh)', permission_attrs) - group_info = {g['cn'][0]: g for g in group_info} + groups = user_group_list(full=True)["groups"] + permissions = user_permission_list(full=True)["permissions"] - for per in ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', - ['cn', 'inheritPermission', 'groupPermission', 'memberUid']): + for permission_name, permission_infos in permissions.items(): - if 'groupPermission' not in per: - per['groupPermission'] = [] - user_permission = set() - for group in per['groupPermission']: - group = group.split("=")[1].split(",")[0] - if 'member' not in group_info[group]: - continue - for user in group_info[group]['member']: - user_permission.add(user) + # These are the users currently allowed because there's an 'inheritPermission' object corresponding to it + currently_allowed_users = set(permission_infos["corresponding_users"]) - if 'inheritPermission' not in per: - per['inheritPermission'] = [] - if 'memberUid' not in per: - per['memberUid'] = [] + # These are the users that should be allowed because they are member of a group that is allowed for this permission ... + should_be_allowed_users = set([user for group in permission_infos["allowed"] for user in groups[group]["members"]]) - uid_val = [v.split("=")[1].split(",")[0] for v in user_permission] - if user_permission == set(per['inheritPermission']) and set(uid_val) == set(per['memberUid']) and not force: + # Note that a LDAP operation with the same value that is in LDAP crash SLAP. + # So we need to check before each ldap operation that we really change something in LDAP + if currently_allowed_users == should_be_allowed_users: + # We're all good, this permission is already correctly synchronized ! continue - inheritPermission = {'inheritPermission': user_permission, 'memberUid': uid_val} - if force: - if per['groupPermission']: - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': []}): - raise YunohostError('permission_update_failed_clear') - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': per['groupPermission']}): - raise YunohostError('permission_update_failed_populate') - if per['inheritPermission']: - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'inheritPermission': []}): - raise YunohostError('permission_update_failed_clear') - if user_permission: - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): - raise YunohostError('permission_update_failed') - else: - if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): - raise YunohostError('permission_update_failed') + + new_inherited_perms = {'inheritPermission': ["uid=%s,ou=users,dc=yunohost,dc=org" % u for u in should_be_allowed_users], + 'memberUid': should_be_allowed_users} + + # Commit the change with the new inherited stuff + if not ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms): + raise YunohostError('permission_update_failed') + logger.debug(m18n.n('permission_generated')) app_ssowatconf() From 98b1c30330af31c7e4bd9e81a0a436f1a3b3c5e2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 21:40:04 +0200 Subject: [PATCH 18/92] Simplify app_ssowatconf code related to permissions --- src/yunohost/app.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c2fb87acf..1a41e2a01 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1414,12 +1414,10 @@ def app_ssowatconf(): skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$") skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$") - permission = {} - for a in user_permission_list()['permissions'].values(): - for p in a.values(): - if 'URL' in p: - for u in p['URL']: - permission[u] = p['allowed_users'] + permissions_per_url = {} + for permission_name, permission_infos in user_permission_list(full=True)['permissions'].items(): + for url in permission_infos["urls"]: + permissions_per_url[url] = permission_infos['corresponding_users'] conf_dict = { 'portal_domain': main_domain, @@ -1441,7 +1439,7 @@ def app_ssowatconf(): 'redirected_regex': redirected_regex, 'users': {username: app_map(user=username) for username in user_list()['users'].keys()}, - 'permission': permission, + 'permissions': permissions_per_url, } with open('/etc/ssowat/conf.json', 'w+') as f: From 38c43f4b9a6f899985fb7bb67d1c9ce7b1cafad9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 22:48:54 +0200 Subject: [PATCH 19/92] Fix the whole operation logger / related to thing + propagate on the legacy addaccess --- locales/en.json | 13 +++++-------- src/yunohost/app.py | 37 +++++++++++++++++++------------------ src/yunohost/log.py | 4 ++-- src/yunohost/permission.py | 31 +++++++++++++++++++++++-------- src/yunohost/user.py | 26 +++++++++++++------------- 5 files changed, 62 insertions(+), 49 deletions(-) diff --git a/locales/en.json b/locales/en.json index ebbb89fa8..2c6363608 100644 --- a/locales/en.json +++ b/locales/en.json @@ -259,9 +259,6 @@ "log_help_to_get_failed_log": "The operation '{desc}' has failed! To get help, please share the full log of this operation using the command 'yunohost log display {name} --share'", "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", - "log_app_addaccess": "Add access to '{}'", - "log_app_removeaccess": "Remove access to '{}'", - "log_app_clearaccess": "Remove all access to '{}'", "log_app_fetchlist": "Add an application list", "log_app_removelist": "Remove an application list", "log_app_change_url": "Change the url of '{}' application", @@ -279,9 +276,9 @@ "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the ip associated with your YunoHost subdomain '{}'", "log_letsencrypt_cert_install": "Install Let's encrypt certificate on '{}' domain", - "log_permission_add": "Add permission '{}' for app '{}'", - "log_permission_remove": "Remove permission '{}'", - "log_permission_update": "Update permission '{}' for app '{}'", + "log_permission_create": "Create permission '{permission}'", + "log_permission_delete": "Delete permission '{permission}'", + "log_permission_urls": "Update urls related to permission '{permission}'", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", @@ -291,8 +288,8 @@ "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", - "log_user_permission_add": "Update '{}' permission", - "log_user_permission_remove": "Update '{}' permission", + "log_user_permission_update": "Update accesses for permission '{permission}'", + "log_user_permission_reset": "Reset permission '{permission}'", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_postinstall": "Postinstall your YunoHost server", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1a41e2a01..14396b1cd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1039,8 +1039,7 @@ def app_remove(operation_logger, app): raise YunohostError("this_action_broke_dpkg") -@is_unit_operation(['permission','app']) -def app_addaccess(operation_logger, apps, users=[]): +def app_addaccess(apps, users=[]): """ Grant access right to users (everyone by default) @@ -1051,15 +1050,15 @@ def app_addaccess(operation_logger, apps, users=[]): """ from yunohost.permission import user_permission_update - permission = user_permission_update(operation_logger, app=apps, permission="main", add_username=users) + output = {} + for app in apps: + permission = user_permission_update(app+".main", add=users) + output[app] = permission["corresponding_users"] - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} - - return {'allowed_users': result} + return {'allowed_users': output} -@is_unit_operation(['permission','app']) -def app_removeaccess(operation_logger, apps, users=[]): +def app_removeaccess(apps, users=[]): """ Revoke access right to users (everyone by default) @@ -1070,15 +1069,15 @@ def app_removeaccess(operation_logger, apps, users=[]): """ from yunohost.permission import user_permission_update - permission = user_permission_update(operation_logger, app=apps, permission="main", del_username=users) + output = {} + for app in apps: + permission = user_permission_update(app+".main", remove=users) + output[app] = permission["corresponding_users"] - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} - - return {'allowed_users': result} + return {'allowed_users': output} -@is_unit_operation(['permission','app']) -def app_clearaccess(operation_logger, apps): +def app_clearaccess(apps): """ Reset access rights for the app @@ -1086,13 +1085,15 @@ def app_clearaccess(operation_logger, apps): apps """ - from yunohost.permission import user_permission_clear + from yunohost.permission import user_permission_reset - permission = user_permission_clear(operation_logger, app=apps, permission="main") + output = {} + for app in apps: + permission = user_permission_reset(app+".main") + output[app] = permission["corresponding_users"] - result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} + return {'allowed_users': output} - return {'allowed_users': result} def app_debug(app): """ diff --git a/src/yunohost/log.py b/src/yunohost/log.py index cbb850e44..8b0f893e8 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -44,7 +44,7 @@ CATEGORIES = ['operation', 'history', 'package', 'system', 'access', 'service', 'app'] METADATA_FILE_EXT = '.yml' LOG_FILE_EXT = '.log' -RELATED_CATEGORIES = ['app', 'domain', 'service', 'user'] +RELATED_CATEGORIES = ['app', 'domain', 'group', 'service', 'user'] logger = getActionLogger('yunohost.log') @@ -213,7 +213,7 @@ def log_display(path, number=None, share=False): return infos -def is_unit_operation(entities=['app', 'domain', 'service', 'user'], +def is_unit_operation(entities=['app', 'domain', 'group', 'service', 'user'], exclude=['password'], operation_key=None): """ Configure quickly a unit operation diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 54aa25e23..a4ff9fb15 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -76,6 +76,7 @@ def user_permission_list(short=False, full=False): return {'permissions': permissions} +@is_unit_operation() def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application @@ -98,6 +99,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, current_allowed_groups = existing_permission["allowed"] all_existing_groups = user_group_list()['groups'].keys() + operation_logger.related_to.append(('app', permission.split(".")[0])) # Compute new allowed group list (and make sure what we're doing make sense) @@ -110,6 +112,8 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: logger.warning(m18n.n('group_already_allowed', permission=permission, group=group)) + else: + operation_logger.related_to.append(('group', group)) new_allowed_groups += groups_to_add @@ -120,6 +124,8 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('group_unknown', group=group) if group not in current_allowed_groups: logger.warning(m18n.n('group_already_disallowed', permission=permission, group=group)) + else: + operation_logger.related_to.append(('group', group)) new_allowed_groups = [g for g in new_allowed_groups if g not in groups_to_remove] @@ -132,15 +138,17 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # FIXME : write a better explanation ? logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the specific groups currently allowed.") - # Commit the new allowed group list - - operation_logger.start() - # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): # FIXME : i18n logger.warning("No change was applied because not relevant modification were found") - elif ldap.update('cn=%s,ou=permission' % permission, + return + + # Commit the new allowed group list + + operation_logger.start() + + if ldap.update('cn=%s,ou=permission' % permission, {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}): logger.debug(m18n.n('permission_updated', permission=permission)) @@ -172,6 +180,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, raise YunohostError('permission_update_failed') +@is_unit_operation() def user_permission_reset(operation_logger, permission, sync_perm=True): """ Reset a given permission to just 'all_users' @@ -191,6 +200,9 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): # Update permission with default (all_users) + operation_logger.related_to.append(('app', permission.split(".")[0])) + operation_logger.start() + default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} if ldap.update('cn=%s,ou=permission' % permission, default_permission): logger.debug(m18n.n('permission_updated', permission=permission)) @@ -228,7 +240,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): # -@is_unit_operation(['permission', 'app']) +@is_unit_operation() def permission_create(operation_logger, permission, urls=None, sync_perm=True): """ Create a new permission for a specific application @@ -267,6 +279,7 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): if urls: attr_dict['URL'] = [_normalize_url(url) for url in urls] + operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() if ldap.add('cn=%s,ou=permission' % permission, attr_dict): if sync_perm: @@ -277,7 +290,7 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): raise YunohostError('permission_creation_failed') -@is_unit_operation(['permission', 'app']) +@is_unit_operation() def permission_urls(operation_logger, permission, add=None, remove=None, sync_perm=True): """ Update urls related to a permission for a specific application @@ -316,6 +329,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe # Actually commit the change + operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() if ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}): if sync_perm: @@ -326,7 +340,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe raise YunohostError('premission_update_failed') -@is_unit_operation(['permission', 'app']) +@is_unit_operation() def permission_delete(operation_logger, permission, force=False, sync_perm=True): """ Delete a permission @@ -349,6 +363,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) # Actually delete the permission + operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() if ldap.remove('cn=%s,ou=permission' % permission): if sync_perm: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 2bf36cfd6..5631a2204 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -525,7 +525,7 @@ def user_group_list(short=False, full=False): return {'groups': groups} -@is_unit_operation([('groupname', 'user')]) +@is_unit_operation([('groupname', 'group')]) def user_group_create(operation_logger, groupname, gid=None, primary_group=False, sync_perm=True): """ Create group @@ -537,8 +537,6 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface - operation_logger.start() - ldap = _get_ldap_interface() # Validate uniqueness of groupname in LDAP @@ -574,6 +572,7 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False if primary_group: attr_dict["member"] = ["uid=" + groupname + ",ou=users,dc=yunohost,dc=org"] + operation_logger.start() if ldap.add('cn=%s,ou=groups' % groupname, attr_dict): logger.success(m18n.n('group_created', group=groupname)) if sync_perm: @@ -583,7 +582,7 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False raise YunohostError('group_creation_failed', group=groupname) -@is_unit_operation([('groupname', 'user')]) +@is_unit_operation([('groupname', 'group')]) def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): """ Delete user @@ -614,7 +613,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): permission_sync_to_user() -@is_unit_operation([('groupname', 'user')]) +@is_unit_operation([('groupname', 'group')]) def user_group_update(operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True): """ Update user informations @@ -650,6 +649,8 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= if user in current_group: logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) + else: + operation_logger.related_to.append(('user', user)) new_group += users_to_add @@ -659,6 +660,8 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= for user in users_to_remove: if user not in current_group: logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) + else: + operation_logger.related_to.append(('user', user)) # Remove users_to_remove from new_group # Kinda like a new_group -= users_to_remove @@ -666,9 +669,8 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= new_group_dns = ["uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group] - operation_logger.start() - if set(new_group) != set(current_group): + operation_logger.start() ldap = _get_ldap_interface() if not ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}): raise YunohostError('group_update_failed', group=groupname) @@ -718,18 +720,16 @@ def user_permission_list(short=False, full=False): return yunohost.permission.user_permission_list(short, full) -@is_unit_operation([('permission', 'user')]) -def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True): +def user_permission_update(permission, add=None, remove=None, sync_perm=True): import yunohost.permission - return yunohost.permission.user_permission_update(operation_logger, permission, + return yunohost.permission.user_permission_update(permission, add=add, remove=remove, sync_perm=sync_perm) -@is_unit_operation([('app', 'user')]) -def user_permission_reset(operation_logger, permission, sync_perm=True): +def user_permission_reset(permission, sync_perm=True): import yunohost.permission - return yunohost.permission.user_permission_reset(operation_logger, permission, + return yunohost.permission.user_permission_reset(permission, sync_perm=sync_perm) From d5b2fb7a7126e80492d38e455bcb41f11e0b32b8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Sep 2019 23:18:23 +0200 Subject: [PATCH 20/92] Misc fixes/improvements for i18n strings --- locales/en.json | 38 ++++++++++++++++++-------------------- src/yunohost/permission.py | 16 ++++++++-------- src/yunohost/user.py | 8 ++++---- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/locales/en.json b/locales/en.json index 2c6363608..e41250759 100644 --- a/locales/en.json +++ b/locales/en.json @@ -228,19 +228,19 @@ "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", - "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled'", - "group_already_disallowed": "Group '{group:s}' already has permission '{permission:s}' disabled'", - "group_name_already_exist": "Group {name:s} already exist", + "group_already_exist": "Group {group} already exist", + "group_already_exist_on_system": "Group {group} already exists in the system group", "group_created": "Group '{group}' successfully created", "group_creation_failed": "Group creation failed for group '{group}'", - "group_cannot_be_edited": "The group {group:s} cannot be edited manually.", - "group_cannot_be_deleted": "The group {group:s} cannot be deleted manually.", + "group_cannot_be_edited": "The group {group} cannot be edited manually.", + "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Group '{group} 'deletion failed", - "group_info_failed": "Group info failed", - "group_unknown": "Group {group:s} unknown", + "group_unknown": "Group {group} unknown", "group_updated": "Group '{group}' updated", "group_update_failed": "Group update failed for group '{group}'", + "group_user_already_in_group": "User {user} is already in group {group}", + "group_user_not_in_group": "User {user} is not in group {group}", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution did not finish properly: {path:s}", "hook_json_return_error": "Failed to read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}", @@ -426,22 +426,23 @@ "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_password_app": "Sorry, passwords should not contain the following characters: {forbidden_chars}", - "permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", - "permission_created": "Permission '{permission:s}' for app {app:s} created", - "permission_creation_failed": "Permission creation failed", - "permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", - "permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed", - "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", - "permission_update_failed": "Permission update failed", - "permission_generated": "The permission database has been updated", - "permission_updated": "Permission '{permission:s}' for app {app:s} updated", + "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled'", + "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'", + "permission_already_exist": "Permission '{permission}' already exists", + "permission_cannot_remove_main": "Removing a main permission is not allowed", + "permission_created": "Permission '{permission}' created", + "permission_creation_failed": "Failed to create permission '{permission}'", + "permission_deleted": "Permission '{permission}' deleted", + "permission_deletion_failed": "Failed to delete permission '{permission}'", + "permission_not_found": "Permission '{permission}' does not seem to exist ?", + "permission_update_failed": "Failed to update permission '{permission}'", + "permission_updated": "Permission '{permission}' updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", "port_unavailable": "Port {port:d} is not available", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create ' or the admin interface.", - "remove_main_permission_not_allowed": "Removing the main permission is not allowed", "regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", "regenconf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but has been kept back.", @@ -527,7 +528,6 @@ "ssowat_conf_updated": "The SSOwat configuration has been updated", "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", - "system_groupname_exists": "Groupname already exists in the system group", "system_upgraded": "The system has been upgraded", "system_username_exists": "Username already exists in the system users", "this_action_broke_dpkg": "This action broke dpkg/apt (the system package managers)... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", @@ -556,14 +556,12 @@ "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", - "user_already_in_group": "User {user:} already in group {group:s}", "user_created": "The user has been created", "user_creation_failed": "Unable to create user", "user_deleted": "The user has been deleted", "user_deletion_failed": "Unable to delete user", "user_home_creation_failed": "Unable to create user home folder", "user_info_failed": "Unable to retrieve user information", - "user_not_in_group": "User {user:s} not in group {group:s}", "user_unknown": "Unknown user: {user:s}", "user_update_failed": "Unable to update user", "user_updated": "The user has been updated", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index a4ff9fb15..21ee960fa 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -111,7 +111,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) if group in current_allowed_groups: - logger.warning(m18n.n('group_already_allowed', permission=permission, group=group)) + logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group)) else: operation_logger.related_to.append(('group', group)) @@ -123,7 +123,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, if group not in all_existing_groups: raise YunohostError('group_unknown', group=group) if group not in current_allowed_groups: - logger.warning(m18n.n('group_already_disallowed', permission=permission, group=group)) + logger.warning(m18n.n('permission_already_disallowed', permission=permission, group=group)) else: operation_logger.related_to.append(('group', group)) @@ -177,7 +177,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, return new_permission else: - raise YunohostError('permission_update_failed') + raise YunohostError('permission_update_failed', permission=permission) @is_unit_operation() @@ -207,7 +207,7 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): if ldap.update('cn=%s,ou=permission' % permission, default_permission): logger.debug(m18n.n('permission_updated', permission=permission)) else: - raise YunohostError('permission_update_failed') + raise YunohostError('permission_update_failed', permission=permission) if sync_perm: permission_sync_to_user() @@ -337,7 +337,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe logger.debug(m18n.n('permission_updated', permission=permission)) return user_permission_list(full=True)["permissions"][permission] else: - raise YunohostError('premission_update_failed') + raise YunohostError('permission_update_failed', permission=permission) @is_unit_operation() @@ -350,7 +350,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) """ if permission.endswith("main") and not force: - raise YunohostError('remove_main_permission_not_allowed') + raise YunohostError('permission_cannot_remove_main') from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() @@ -406,9 +406,9 @@ def permission_sync_to_user(): # Commit the change with the new inherited stuff if not ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms): - raise YunohostError('permission_update_failed') + raise YunohostError('permission_update_failed', permission=permission_name) - logger.debug(m18n.n('permission_generated')) + logger.debug("The permission database has been resynchronized") app_ssowatconf() diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 5631a2204..e1719d3a6 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -544,12 +544,12 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False 'cn': groupname }, base_dn='ou=groups,dc=yunohost,dc=org') if conflict: - raise YunohostError('group_name_already_exist', name=groupname) + raise YunohostError('group_already_exist', group=groupname) # Validate uniqueness of groupname in system group all_existing_groupnames = {x.gr_name for x in grp.getgrall()} if groupname in all_existing_groupnames: - raise YunohostError('system_groupname_exists') + raise YunohostError('group_already_exist_on_system', group=groupname) if not gid: # Get random GID @@ -648,7 +648,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= raise YunohostError('user_unknown', user=user) if user in current_group: - logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) + logger.warning(m18n.n('group_user_already_in_group', user=user, group=groupname)) else: operation_logger.related_to.append(('user', user)) @@ -659,7 +659,7 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= for user in users_to_remove: if user not in current_group: - logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) + logger.warning(m18n.n('group_user_not_in_group', user=user, group=groupname)) else: operation_logger.related_to.append(('user', user)) From a92ff5307774d657b601051b31e0e52634472180 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 00:20:22 +0200 Subject: [PATCH 21/92] Propagate changes to other parts of the code relying on groups and permissions --- src/yunohost/app.py | 29 +++++++----------- src/yunohost/backup.py | 61 ++++++++++++++++---------------------- src/yunohost/permission.py | 9 ++++-- 3 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 14396b1cd..f505dd088 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -465,7 +465,7 @@ def app_change_url(operation_logger, app, domain, path): """ from yunohost.hook import hook_exec, hook_callback from yunohost.domain import _normalize_domain_path, _get_conflicting_apps - from yunohost.permission import permission_update + from yunohost.permission import permission_urls installed = _is_installed(app) if not installed: @@ -555,7 +555,7 @@ def app_change_url(operation_logger, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) - permission_update(app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path], sync_perm=True) + permission_urls(app+".main", add=[domain+path], remove=[old_domain+old_path], sync_perm=True) # avoid common mistakes if _run_service_command("reload", "nginx") is False: @@ -738,7 +738,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import permission_create, permission_update, permission_delete, permission_sync_to_user + from yunohost.permission import permission_create, permission_urls, permission_delete, permission_sync_to_user ldap = _get_ldap_interface() # Fetch or extract sources @@ -875,7 +875,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Create permission before the install (useful if the install script redefine the permission) # Note that sync_perm is disabled to avoid triggering a whole bunch of code and messages # can't be sure that we don't have one case when it's needed - permission_create(app=app_instance_name, permission="main", sync_perm=False) + permission_create(app_instance_name+".main", sync_perm=False) # Execute the app install script install_retcode = 1 @@ -910,11 +910,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args=[app_instance_name], env=env_dict_remove )[0] # Remove all permission in LDAP - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) - permission_list = [p['cn'][0] for p in result] - for l in permission_list: - permission_delete(app_instance_name, l.split('.')[0], force=True) + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app_instance_name+"."): + permission_delete(permission_name, force=True) if remove_retcode != 0: msg = m18n.n('app_not_properly_removed', @@ -960,8 +958,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu domain = app_settings.get('domain', None) path = app_settings.get('path', None) if domain and path: - permission_update(app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) - + permission_urls(app_instance_name+".main", add=[domain+path], sync_perm=False) permission_sync_to_user() logger.success(m18n.n('installation_complete')) @@ -978,7 +975,6 @@ def app_remove(operation_logger, app): app -- App(s) to delete """ - from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_exec, hook_remove, hook_callback from yunohost.permission import permission_delete, permission_sync_to_user if not _is_installed(app): @@ -1026,12 +1022,9 @@ def app_remove(operation_logger, app): hook_remove(app) # Remove all permission in LDAP - ldap = _get_ldap_interface() - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app, attrs=['cn']) - permission_list = [p['cn'][0] for p in result] - for l in permission_list: - permission_delete(app, l.split('.')[0], force=True, sync_perm=False) + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app+"."): + permission_delete(permission_name, force=True, sync_perm=False) permission_sync_to_user() diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index fdbd8c62c..0527f89bf 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -703,6 +703,10 @@ class BackupManager(): self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) # backup permissions + # + # FIXME : why can't we instead use a simple yaml file to store the + # relevant info and recreate the permission object from it ... + # logger.debug(m18n.n('backup_permission', app=app)) ldap_url = "ldap:///dc=yunohost,dc=org???(&(objectClass=permissionYnh)(cn=*.%s))" % app os.system("slapcat -b dc=yunohost,dc=org -H '%s' -l '%s/permission.ldif'" % (ldap_url, settings_dir)) @@ -919,7 +923,7 @@ class RestoreManager(): successfull_apps = self.targets.list("apps", include=["Success", "Warning"]) - permission_sync_to_user(force=False) + permission_sync_to_user() if os.path.ismount(self.work_dir): ret = subprocess.call(["umount", self.work_dir]) @@ -1183,18 +1187,11 @@ class RestoreManager(): if system_targets == []: return - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() + from yunohost.permission import permission_create, user_permission_update, user_permission_list # Backup old permission for apps # We need to do that because in case of an app is installed we can't remove the permission for this app - old_apps_permission = [] - try: - old_apps_permission = ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.xmpp))(!(cn=main.sftp)))', - ['cn', 'objectClass', 'groupPermission', 'URL', 'gidNumber']) - except: - logger.info(m18n.n('apps_permission_not_found')) + old_apps_permission = user_permission_list(ignore_system_perms=True)["permissions"] # Start register change on system operation_logger = OperationLogger('backup_restore_system') @@ -1232,12 +1229,11 @@ class RestoreManager(): regen_conf() - # Check if we need to do the migration 0009 : setup group and permission + # Check that at least a group exists (all_users) to know if we need to + # do the migration 0011 : setup group and permission + # # Legacy code - result = ldap.search('ou=groups,dc=yunohost,dc=org', - '(&(objectclass=groupOfNamesYnh)(cn=all_users))', - ['cn']) - if not result: + if not user_group_list["groups"]: from yunohost.tools import _get_migration_by_name setup_group_permission = _get_migration_by_name("setup_group_permission") # Update LDAP schema restart slapd @@ -1245,22 +1241,16 @@ class RestoreManager(): regen_conf(names=['slapd'], force=True) setup_group_permission.migrate_LDAP_db() - # Remove all permission for all app which sill in the LDAP - for per in ldap.search('ou=permission,dc=yunohost,dc=org', - '(&(objectClass=permissionYnh)(!(cn=mail.main))(!(cn=xmpp.main))(!(cn=sftp.main)))', - ['cn']): - if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]): - raise YunohostError('permission_deletion_failed', - permission=per['cn'][0].split('.')[0], - app=per['cn'][0].split('.')[1]) + # Remove all permission for all app which is still in the LDAP + for permission_name in user_permission_list(ignore_system_perms=True)["permissions"].keys(): + permission_delete(permission_name) # Restore permission for the app which is installed - for per in old_apps_permission: - # FIXME : will come here later to fix this following previous commits ... - permission_name, app_name = per['cn'][0].split('.') + for permission_name, permission_infos in old_apps_permission.items(): + app_name = permission_name.split(".")[0] if _is_installed(app_name): - if not ldap.add('cn=%s,ou=permission' % per['cn'][0], per): - raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name) + permission_create(permission_name, urls=permission_infos["urls"], sync_perm=False) + user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"]) def _restore_apps(self): @@ -1368,6 +1358,10 @@ class RestoreManager(): restore_script = os.path.join(tmp_folder_for_app_restore, 'restore') # Restore permissions + # + # FIXME : why can't we instead use a simple yaml file to store the + # relevant info and recreate the permission object from it ... + # if os.path.isfile(app_settings_in_archive + '/permission.ldif'): filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass', 'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid'] @@ -1422,7 +1416,6 @@ class RestoreManager(): operation_logger.start() # Execute remove script - # TODO: call app_remove instead if hook_exec(remove_script, args=[app_instance_name], env=env_dict_remove)[0] != 0: msg = m18n.n('app_not_properly_removed', app=app_instance_name) @@ -1434,12 +1427,10 @@ class RestoreManager(): # Cleaning app directory shutil.rmtree(app_settings_new_path, ignore_errors=True) - # Remove all permission in LDAP - result = ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) - permission_list = [p['cn'][0] for p in result] - for l in permission_list: - permission_delete(app_instance_name, l.split('.')[0], force=True) + # Remove all permission in LDAP for this app + for permission_name in user_permission_list()["permissions"].keys(): + if permission_name.startswith(app_instance_name+"."): + permission_delete(permission_name, force=True) # TODO Cleaning app hooks else: diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 21ee960fa..4d935d3c0 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -36,6 +36,8 @@ from yunohost.log import is_unit_operation logger = getActionLogger('yunohost.user') +SYSTEM_PERMS = ["mail", "xmpp", "stfp"] + # # # The followings are the methods exposed through the "yunohost user permission" interface @@ -43,7 +45,7 @@ logger = getActionLogger('yunohost.user') # -def user_permission_list(short=False, full=False): +def user_permission_list(short=False, full=False, ignore_system_perms=True): """ List permissions and corresponding accesses """ @@ -62,8 +64,11 @@ def user_permission_list(short=False, full=False): for infos in permissions_infos: name = infos['cn'][0] - permissions[name] = {} + if ignore_system_perms and name.split(".")[0] in SYSTEM_PERMS: + continue + + permissions[name] = {} permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])] if full: From bbfc62cf3efc298fc297fa4da52d45e420cc0c0c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 00:41:38 +0200 Subject: [PATCH 22/92] Backup/restore app permissions using yaml files which are much simpler to handle... --- locales/en.json | 1 - src/yunohost/backup.py | 55 +++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/locales/en.json b/locales/en.json index e41250759..e69e06201 100644 --- a/locales/en.json +++ b/locales/en.json @@ -50,7 +50,6 @@ "app_upgrade_some_app_failed": "Unable to upgrade some applications", "app_upgraded": "{app:s} has been upgraded", "apps_permission_not_found": "No permission found for the installed apps", - "apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed", "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", "appslist_could_not_migrate": "Could not migrate app list {appslist:s}! Unable to parse the url… The old cron job has been kept in {bkp_file:s}.", "appslist_fetched": "The application list {appslist:s} has been fetched", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 0527f89bf..f1ac7ee9c 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -40,7 +40,7 @@ from moulinette import msignals, m18n from yunohost.utils.error import YunohostError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_file, mkdir +from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml from yunohost.app import ( app_info, _is_installed, _parse_app_instance_name, _patch_php5 @@ -677,6 +677,8 @@ class BackupManager(): backup_app_failed -- Raised at the end if the app backup script execution failed """ + from yunohost.permission import user_permission_list + app_setting_path = os.path.join('/etc/yunohost/apps/', app) # Prepare environment @@ -703,13 +705,10 @@ class BackupManager(): self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) # backup permissions - # - # FIXME : why can't we instead use a simple yaml file to store the - # relevant info and recreate the permission object from it ... - # logger.debug(m18n.n('backup_permission', app=app)) - ldap_url = "ldap:///dc=yunohost,dc=org???(&(objectClass=permissionYnh)(cn=*.%s))" % app - os.system("slapcat -b dc=yunohost,dc=org -H '%s' -l '%s/permission.ldif'" % (ldap_url, settings_dir)) + permissions = user_permission_list(full=True)["permissions"] + this_app_permissions = {name: infos for name, infos in permissions.items() if name.startswith(app + ".")} + write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions) except: abs_tmp_app_dir = os.path.join(self.work_dir, 'apps/', app) @@ -1289,11 +1288,8 @@ class RestoreManager(): name already exists restore_app_failed -- Raised if the restore bash script failed """ - from moulinette.utils.filesystem import read_ldif from yunohost.user import user_group_list - from yunohost.permission import permission_delete - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() + from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update def copytree(src, dst, symlinks=False, ignore=None): for item in os.listdir(src): @@ -1358,26 +1354,25 @@ class RestoreManager(): restore_script = os.path.join(tmp_folder_for_app_restore, 'restore') # Restore permissions - # - # FIXME : why can't we instead use a simple yaml file to store the - # relevant info and recreate the permission object from it ... - # - if os.path.isfile(app_settings_in_archive + '/permission.ldif'): - filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass', - 'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid'] - entries = read_ldif('%s/permission.ldif' % app_settings_in_archive, filtred_entries) - group_list = user_group_list()['groups'] - for dn, entry in entries: - # Remove the group which has been removed - for group in entry['groupPermission']: - group_name = group.split(',')[0].split('=')[1] - if group_name not in group_list: - entry['groupPermission'].remove(group) - if not ldap.add('cn=%s,ou=permission' % entry['cn'][0], entry): - raise YunohostError('apps_permission_restoration_failed', - permission=entry['cn'][0].split('.')[0], - app=entry['cn'][0].split('.')[1]) + if os.path.isfile('%s/permissions.yml' % app_settings_new_path): + + permissions = read_yaml('%s/permissions.yml' % app_settings_new_path) + existing_groups = user_group_list()['groups'] + + for permission_name, permission_infos in permissions: + + permission_create(permission_name, urls=permission_infos.get("urls", [])) + + if "allowed" not in permissions_infos: + logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s ... You might need to reconfigure permissions yourself!" % (permission_name, app_instance_name)) + else: + groups = [g for g in permission_infos["allowed"] if g in existing_groups] + user_permission_update(permission_name, remove="all_users", add=groups) + + os.remove('%s/permissions.yml' % app_settings_new_path) else: + # Otherwise, we need to migrate the legacy permissions of this + # app (included in its settings.yml) from yunohost.tools import _get_migration_by_name setup_group_permission = _get_migration_by_name("setup_group_permission") setup_group_permission.migrate_app_permission(app=app_instance_name) From e40698ef2062346638a4492924a4dacf32f081f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 02:25:52 +0200 Subject: [PATCH 23/92] Propagate changes on migration --- locales/en.json | 2 +- .../0011_setup_group_permission.py | 20 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index e69e06201..c370f821e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -195,7 +195,6 @@ "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", - "error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", @@ -355,6 +354,7 @@ "migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0011_create_group": "Creating a group for each user...", "migration_0011_done": "Migration successful. You are now able to manage groups of users.", + "migration_0011_error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration need to be updated.\nYou need to save your actual configuration, reintialize the original configuration by the command 'yunohost tools regen-conf -f' and after retry the migration", "migration_0011_LDAP_update_failed": "LDAP update failed. Error: {error:s}", "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index d2924f0af..720e4ac36 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -1,17 +1,16 @@ -import yaml import time import os from moulinette import m18n from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_yaml from yunohost.tools import Migration from yunohost.user import user_group_create, user_group_update from yunohost.app import app_setting, app_list from yunohost.regenconf import regen_conf -from yunohost.permission import permission_create, permission_sync_to_user -from yunohost.user import user_permission_add +from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user logger = getActionLogger('yunohost.migration') @@ -19,6 +18,7 @@ logger = getActionLogger('yunohost.migration') # Tools used also for restoration ################################################### + class MyMigration(Migration): """ Update the LDAP DB to be able to store the permission @@ -38,10 +38,9 @@ class MyMigration(Migration): try: ldap.remove('cn=sftpusers,ou=groups') except: - logger.warn(m18n.n("error_when_removing_sftpuser_group")) + logger.warn(m18n.n("migration_0011_error_when_removing_sftpuser_group")) - with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: - ldap_map = yaml.load(f) + ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') try: attr_dict = ldap_map['parents']['ou=permission'] @@ -65,11 +64,9 @@ class MyMigration(Migration): username = user_info['uid'][0] ldap.update('uid=%s,ou=users' % username, {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) - user_group_create(username, gid=user_info['uidNumber'][0], sync_perm=False) - user_group_update(groupname=username, add=username, force=True, sync_perm=False) + user_group_create(username, gid=user_info['uidNumber'][0], primary_group=True, sync_perm=False) user_group_update(groupname='all_users', add=username, force=True, sync_perm=False) - def migrate_app_permission(self, app=None): logger.info(m18n.n("migration_0011_migrate_permission")) @@ -85,13 +82,12 @@ class MyMigration(Migration): domain = app_setting(app, 'domain') urls = [domain + path] if domain and path else None - permission_create(app, permission='main', urls=urls, default_allow=True, sync_perm=False) + permission_create(app+".main", urls=urls, sync_perm=False) if permission: allowed_group = permission.split(',') - user_permission_add([app], permission='main', group=allowed_group, sync_perm=False) + user_permission_update(app+".main", remove="all_users", add=allowed_group, sync_perm=False) app_setting(app, 'allowed_users', delete=True) - def run(self): # Check if the migration can be processed ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) From c0361430e26d42e0b8f167a330a20abff854655d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 04:02:03 +0200 Subject: [PATCH 24/92] Try to simplify + comment the code of check_LDAP_db_integrity --- src/yunohost/tests/test_permission.py | 79 +++++++++++++++------------ 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 8222248b1..0373a6cbf 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -2,8 +2,10 @@ import pytest from moulinette.core import MoulinetteError from yunohost.app import app_install, app_remove, app_change_url, app_list -from yunohost.user import user_list, user_create, user_permission_list, user_delete, user_group_list, user_group_delete, user_permission_add, user_permission_remove, user_permission_clear -from yunohost.permission import permission_create, permission_update, permission_delete + +from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ + user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info +from yunohost.permission import user_permission_update, user_permission_list, permission_create, permission_urls, permission_delete from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError @@ -57,7 +59,7 @@ def check_LDAP_db_integrity(): # One part should be done automatically by the "memberOf" overlay of LDAP. # The other part is done by the the "permission_sync_to_user" function of the permission module - from yunohost.utils.ldap import _get_ldap_interface + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() user_search = ldap.search('ou=users,dc=yunohost,dc=org', @@ -76,60 +78,65 @@ def check_LDAP_db_integrity(): for user in user_search: user_dn = 'uid=' + user['uid'][0] + ',ou=users,dc=yunohost,dc=org' - group_list = [m.split("=")[1].split(",")[0] for m in user['memberOf']] - permission_list = [] - if 'permission' in user: - permission_list = [m.split("=")[1].split(",")[0] for m in user['permission']] + group_list = [_ldap_path_extract(m, "cn") for m in user['memberOf']] + permission_list = [_ldap_path_extract(m, "cn") for m in user.get('permission', [])] + # This user's DN sould be found in all groups it is a member of for group in group_list: assert user_dn in group_map[group]['member'] + + # This user's DN should be found in all perms it has access to for permission in permission_list: assert user_dn in permission_map[permission]['inheritPermission'] for permission in permission_search: permission_dn = 'cn=' + permission['cn'][0] + ',ou=permission,dc=yunohost,dc=org' - user_list = [] - group_list = [] - if 'inheritPermission' in permission: - user_list = [m.split("=")[1].split(",")[0] for m in permission['inheritPermission']] - assert set(user_list) == set(permission['memberUid']) - if 'groupPermission' in permission: - group_list = [m.split("=")[1].split(",")[0] for m in permission['groupPermission']] + # inheritPermission uid's should match memberUids + user_list = [_ldap_path_extract(m, "uid") for m in permission.get('inheritPermission', [])] + assert set(user_list) == set(permission.get('memberUid', [])) + + # This perm's DN should be found on all related users it is related to for user in user_list: assert permission_dn in user_map[user]['permission'] + + # Same for groups : we should find the permission's DN for all related groups + group_list = [_ldap_path_extract(m, "cn") for m in permission.get('groupPermission', [])] for group in group_list: assert permission_dn in group_map[group]['permission'] - if 'member' in group_map[group]: - user_list_in_group = [m.split("=")[1].split(",")[0] for m in group_map[group]['member']] - assert set(user_list_in_group) <= set(user_list) + + # The list of user in the group should be a subset of all users related to the current permission + users_in_group = [_ldap_path_extract(m, "uid") for m in group_map[group].get("member", [])] + assert set(users_in_group) <= set(user_list) for group in group_search: group_dn = 'cn=' + group['cn'][0] + ',ou=groups,dc=yunohost,dc=org' - user_list = [] - permission_list = [] - if 'member' in group: - user_list = [m.split("=")[1].split(",")[0] for m in group['member']] - if group['cn'][0] in user_list: - # If it's the main group of the user it's normal that it is not in the memberUid - g_list = list(user_list) - g_list.remove(group['cn'][0]) - if 'memberUid' in group: - assert set(g_list) == set(group['memberUid']) - else: - assert g_list == [] - else: - assert set(user_list) == set(group['memberUid']) - if 'permission' in group: - permission_list = [m.split("=")[1].split(",")[0] for m in group['permission']] + user_list = [_ldap_path_extract(m, "uid") for m in group.get("member", [])] + # For primary groups, we should find that : + # - len(user_list) is 1 (a primary group has only 1 member) + # - the group name should be an existing yunohost user + # - memberUid is empty (meaning no other member than the corresponding user) + if group['cn'][0] in user_list: + assert len(user_list) == 1 + assert group["cn"][0] in user_map + assert group.get('memberUid', []) == [] + # Otherwise, user_list and memberUid should have the same content + else: + assert set(user_list) == set(group.get('memberUid', [])) + + # For all users members, this group should be in the "memberOf" on the other side for user in user_list: assert group_dn in user_map[user]['memberOf'] + + # For all the permissions of this group, the group should be among the "groupPermission" on the other side + permission_list = [_ldap_path_extract(m, "cn") for m in group.get('permission', [])] for permission in permission_list: assert group_dn in permission_map[permission]['groupPermission'] - if 'inheritPermission' in permission_map: - allowed_user_list = [m.split("=")[1].split(",")[0] for m in permission_map[permission]['inheritPermission']] - assert set(user_list) <= set(allowed_user_list) + + # And the list of user of this group (user_list) should be a subset of all allowed users for this perm... + allowed_user_list = [_ldap_path_extract(m, "uid") for m in permission_map[permission].get('inheritPermission', [])] + assert set(user_list) <= set(allowed_user_list) def check_permission_for_apps(): From f1f65137965039bca3cc4d7b531b10babfadcd61 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 04:02:36 +0200 Subject: [PATCH 25/92] Small tweaks for user group tests --- src/yunohost/tests/test_user-group.py | 86 ++++++++++++--------------- 1 file changed, 39 insertions(+), 47 deletions(-) diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 34e515ea0..88644c3e6 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,7 +1,8 @@ import pytest from moulinette.core import MoulinetteError -from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_create, user_group_delete, user_group_update, user_group_info +from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ + user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError from yunohost.tests.test_permission import check_LDAP_db_integrity @@ -82,12 +83,13 @@ def test_del_user(): assert "alice" not in group_res assert "alice" not in group_res['all_users']['members'] -def test_add_group(): +def test_create_group(): user_group_create("adminsys") group_res = user_group_list()['groups'] assert "adminsys" in group_res - assert "members" not in group_res['adminsys'] + assert "members" in group_res['adminsys'].keys() + assert group_res["adminsys"]["members"] == [] def test_del_group(): user_group_delete("dev") @@ -99,112 +101,102 @@ def test_del_group(): # Error on create / remove function # -def test_add_bad_user_1(): - # Check email already exist +def test_create_user_with_mail_address_already_taken(): with pytest.raises(MoulinetteError): user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh") -def test_add_bad_user_2(): - # Check to short password +def test_create_user_with_password_too_simple(): with pytest.raises(MoulinetteError): user_create("other", "Alice", "White", "other@" + maindomain, "12") -def test_add_bad_user_3(): - # Check user already exist +def test_create_user_already_exists(): with pytest.raises(MoulinetteError): user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh") -def test_del_bad_user_1(): - # Check user not found +def test_del_user_that_does_not_exist(): with pytest.raises(MoulinetteError): - user_delete("not_exit") + user_delete("doesnt_exist") -def test_add_bad_group_1(): +def test_create_group_all_users(): # Check groups already exist with special group "all_users" with pytest.raises(YunohostError): user_group_create("all_users") -def test_add_bad_group_2(): - # Check groups already exist (for standard groups) +def test_create_group_already_exists(): + # Check groups already exist (regular groups) with pytest.raises(MoulinetteError): user_group_create("dev") -def test_del_bad_group_1(): - # Check not allowed to remove this groups +def test_del_group_all_users(): with pytest.raises(YunohostError): user_group_delete("all_users") -def test_del_bad_group_2(): - # Check groups not found +def test_del_group_that_does_not_exist(): with pytest.raises(MoulinetteError): - user_group_delete("not_exit") + user_group_delete("doesnt_exist") # # Update function # -def test_update_user_1(): +def test_update_user(): user_update("alice", firstname="NewName", lastname="NewLast") info = user_info("alice") - assert "NewName" == info['firstname'] - assert "NewLast" == info['lastname'] + assert info['firstname'] == "NewName" + assert info['lastname'] == "NewLast" -def test_update_group_1(): +def test_update_group_add_user(): user_group_update("dev", add=["bob"]) group_res = user_group_list()['groups'] - assert set(["alice", "bob"]) == set(group_res['dev']['members']) + assert set(group_res['dev']['members']) == set(["alice", "bob"]) -def test_update_group_2(): - # Try to add a user in a group when the user is already in +def test_update_group_add_user_already_in(): user_group_update("apps", add=["bob"]) group_res = user_group_list()['groups'] - assert ["bob"] == group_res['apps']['members'] + assert group_res['apps']['members'] == ["bob"] -def test_update_group_3(): - # Try to remove a user in a group +def test_update_group_remove_user(): user_group_update("apps", remove=["bob"]) group_res = user_group_list()['groups'] - assert "members" not in group_res['apps'] + assert group_res['apps']['members'] == [] -def test_update_group_4(): - # Try to remove a user in a group when it is not already in +def test_update_group_remove_user_not_already_in(): user_group_update("apps", remove=["jack"]) group_res = user_group_list()['groups'] - assert ["bob"] == group_res['apps']['members'] + assert group_res['apps']['members'] == ["bob"] # # Error on update functions # -def test_bad_update_user_1(): - # Check user not found +def test_update_user_that_doesnt_exist(): with pytest.raises(YunohostError): - user_update("not_exit", firstname="NewName", lastname="NewLast") + user_update("doesnt_exist", firstname="NewName", lastname="NewLast") - -def bad_update_group_1(): +def test_update_group_that_doesnt_exist(): # Check groups not found with pytest.raises(YunohostError): - user_group_update("not_exit", add=["alice"]) + user_group_update("doesnt_exist", add=["alice"]) -def test_bad_update_group_2(): - # Check remove user in groups "all_users" not allowed +def test_update_group_all_users_manually(): with pytest.raises(YunohostError): user_group_update("all_users", remove=["alice"]) -def test_bad_update_group_3(): - # Check remove user in it own group not allowed + assert "alice" in user_group_list()["groups"]["all_users"]["members"] + +def test_update_group_primary_manually(): with pytest.raises(YunohostError): user_group_update("alice", remove=["alice"]) + assert "alice" in user_group_list()["groups"]["alice"]["members"] -def test_bad_update_group_1(): +def test_update_group_add_user_that_doesnt_exist(): # Check add bad user in group with pytest.raises(YunohostError): - user_group_update("dev", add=["not_exist"]) + user_group_update("dev", add=["doesnt_exist"]) - assert "not_exist" not in user_group_list()["groups"]["dev"] + assert "doesnt_exist" not in user_group_list()["groups"]["dev"]["members"] From 68db93cd635a8ce5720f3e8b1ca105e6fa4a27d4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 04:03:04 +0200 Subject: [PATCH 26/92] Fix an issue about groups not being properly cleaned and perms synced when deleting a user --- src/yunohost/user.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index e1719d3a6..ef2a7d523 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -245,9 +245,18 @@ def user_delete(operation_logger, username, purge=False): """ from yunohost.hook import hook_callback from yunohost.utils.ldap import _get_ldap_interface + from yunohost.permission import permission_sync_to_user operation_logger.start() + user_group_update("all_users", remove=username, force=True, sync_perm=False) + for group, infos in user_group_list()["groups"].items(): + # If the user is in this group (and it's not the primary group), + # remove the member from the group + if username != group and username in infos["members"]: + user_group_update(group, remove=username, sync_perm=False) + user_group_delete(username, force=True, sync_perm=True) + ldap = _get_ldap_interface() if ldap.remove('uid=%s,ou=users' % username): # Invalidate passwd to take user deletion into account @@ -259,19 +268,6 @@ def user_delete(operation_logger, username, purge=False): else: raise YunohostError('user_deletion_failed') - user_group_delete(username, force=True, sync_perm=True) - - group_list = ldap.search('ou=groups,dc=yunohost,dc=org', - '(&(objectclass=groupOfNamesYnh)(memberUid=%s))' - % username, ['cn']) - for group in group_list: - user_list = ldap.search('ou=groups,dc=yunohost,dc=org', - 'cn=' + group['cn'][0], - ['memberUid'])[0] - user_list['memberUid'].remove(username) - if not ldap.update('cn=%s,ou=groups' % group['cn'][0], user_list): - raise YunohostError('group_update_failed') - hook_callback('post_user_delete', args=[username, purge]) logger.success(m18n.n('user_deleted')) From fe8f7f2210d9cc398dc27f9fa16b20cc11bd79b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 17:49:14 +0200 Subject: [PATCH 27/92] Update permission helper : have a single helper to manage urls, and a helper to add/remove groups to permission --- data/helpers.d/setting | 49 +++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 0e432d916..502da1ed7 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -261,32 +261,51 @@ ynh_permission_delete() { yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)" } -# Add a path managed by the SSO +# Manage urls related to a permission # -# usage: ynh_permission_add_url --permission "permission" --url "url" ["url" ...] +# usage: ynh_permission_urls --permission "permission" --add "url" ["url" ...] --remove "url" ["url" ...] # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# | arg: add - (optional) a list of FULL urls to add to the permission (e.g. domain.tld/apps/admin) +# | arg: remove - (optional) a list of FULL urls to remove from the permission (e.g. other.tld/apps/admin) # -ynh_permission_add_url() { - declare -Ar args_array=([p]=permission= [u]=urls= ) +ynh_permission_urls() { + declare -Ar args_array=([p]=permission= [a]=add= [r]=remove=) local permission - local urls + local add + local remove ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission', add=['${urls//';'/"','"}'], sync_perm=False)" + if [[ -n ${add:-} ]]; then + add=",add=['${add//';'/"','"}']" + fi + if [[ -n ${remove:-} ]]; then + remove=",remove=['${remove//';'/"','"}']" + fi + + yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission' ${add:-} ${remove:-})" } -# Remove a path managed by the SSO +# Update a permission for the app # -# usage: ynh_permission_del_path --app "app" --permission "permission" --url "url" ["url" ...] -# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) -# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin) +# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...] +# | arg: permission - the name for the permission (by default a permission named "main" already exist) +# | arg: add - the list of group or users to enable add to the permission +# | arg: remove - the list of group or users to remove from the permission # -ynh_permission_remove_url() { - declare -Ar args_array=([p]=permission= [u]=urls= ) +# example: ynh_permission_update --permission admin --add samdoe --remove all_users +ynh_permission_update() { + declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= ) local permission - local urls + local add + local remove ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission', remove=['${url//';'/"','"}'], sync_perm=False)" + if [[ -n ${add:-} ]]; then + add="--add ${add//';'/" "}" + fi + if [[ -n ${remove:-} ]]; then + remove="--remove ${remove//';'/" "} " + fi + + yunohost user permission update "$app.$permission" ${add:-} ${remove:-} } From b912cd0aecd48ec36d87d611447c91c97d293939 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 17:49:35 +0200 Subject: [PATCH 28/92] Propagate all changes to tests --- src/yunohost/tests/test_permission.py | 353 +++++++++++--------------- 1 file changed, 153 insertions(+), 200 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 0373a6cbf..2df6362a9 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -5,7 +5,8 @@ from yunohost.app import app_install, app_remove, app_change_url, app_list from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info -from yunohost.permission import user_permission_update, user_permission_list, permission_create, permission_urls, permission_delete +from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \ + permission_create, permission_urls, permission_delete from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError @@ -20,20 +21,18 @@ def clean_user_groups_permission(): if g != "all_users": user_group_delete(g) - for a, per in user_permission_list()['permissions'].items(): - if a in ['wiki', 'blog', 'site']: - for p in per: - permission_delete(a, p, force=True, sync_perm=False) + for p in user_permission_list()['permissions']: + if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]): + permission_delete(p, force=True, sync_perm=False) def setup_function(function): clean_user_groups_permission() user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") - permission_create("wiki", "main", [maindomain + "/wiki"], sync_perm=False) - permission_create("blog", "main", sync_perm=False) - - user_permission_add(["blog"], "main", group="alice") + permission_create("wiki.main", urls=[maindomain + "/wiki"], sync_perm=False) + permission_create("blog.main", sync_perm=False) + user_permission_update("blog.main", remove="all_users", add="alice") def teardown_function(function): clean_user_groups_permission() @@ -144,100 +143,89 @@ def check_permission_for_apps(): # and we don't have any permission linked to no apps. The only exception who is not liked to an app # is mail, xmpp, and sftp - from yunohost.utils.ldap import _get_ldap_interface - ldap = _get_ldap_interface() - permission_search = ldap.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', - ['cn', 'groupPermission', 'inheritPermission', 'memberUid']) + app_perms = user_permission_list(ignore_system_perms=True)["permissions"].keys() + + # Keep only the prefix so that + # ["foo.main", "foo.pwet", "bar.main"] + # becomes + # {"bar", "foo"} + # and compare this to the list of installed apps ... + + app_perms_prefix = set(p.split(".")[0] for p in app_perms) installed_apps = {app['id'] for app in app_list(installed=True)['apps']} - permission_list_set = {permission['cn'][0].split(".")[1] for permission in permission_search} - extra_service_permission = set(['mail', 'xmpp']) - if 'sftp' in permission_list_set: - extra_service_permission.add('sftp') - assert installed_apps == permission_list_set - extra_service_permission + assert installed_apps == app_perms_prefix # # List functions # -def test_list_permission(): - res = user_permission_list()['permissions'] +def test_permission_list(): + res = user_permission_list(full=True)['permissions'] - assert "wiki" in res - assert "main" in res['wiki'] - assert "blog" in res - assert "main" in res['blog'] - assert "mail" in res - assert "main" in res['mail'] - assert "xmpp" in res - assert "main" in res['xmpp'] - assert ["all_users"] == res['wiki']['main']['allowed_groups'] - assert ["alice"] == res['blog']['main']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) - assert ["alice"] == res['blog']['main']['allowed_users'] - assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] + assert "wiki.main" in res + assert "blog.main" in res + assert "mail.main" in res + assert "xmpp.main" in res + assert res['wiki.main']['allowed'] == ["all_users"] + assert res['blog.main']['allowed'] == ["alice"] + assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) + assert res['blog.main']['corresponding_users'] == ["alice"] + assert res['wiki.main']['urls'] == [maindomain + "/wiki"] # # Create - Remove functions # -def test_add_permission_1(): - permission_create("site", "test") +def test_permission_create_main(): + permission_create("site.main") + + res = user_permission_list(full=True)['permissions'] + assert "site.main" in res + assert res['site.main']['allowed'] == ["all_users"] + assert set(res['site.main']['corresponding_users']) == set(["alice", "bob"]) + + +def test_permission_create_extra(): + permission_create("site.test") + + res = user_permission_list(full=True)['permissions'] + assert "site.test" in res + # all_users is only enabled by default on .main perms + assert "all_users" not in res['site.test']['allowed'] + assert res['site.test']['corresponding_users'] == [] + +def test_permission_delete(): + permission_delete("wiki.main", force=True) res = user_permission_list()['permissions'] - assert "site" in res - assert "test" in res['site'] - assert "all_users" in res['site']['test']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['site']['test']['allowed_users']) - -def test_add_permission_2(): - permission_create("site", "main", default_allow=False) - - res = user_permission_list()['permissions'] - assert "site" in res - assert "main" in res['site'] - assert [] == res['site']['main']['allowed_groups'] - assert [] == res['site']['main']['allowed_users'] - -def test_remove_permission(): - permission_delete("wiki", "main", force=True) - - res = user_permission_list()['permissions'] - assert "wiki" not in res + assert "wiki.main" not in res # # Error on create - remove function # -def test_add_bad_permission(): - # Create permission with same name +def test_permission_create_already_existing(): with pytest.raises(YunohostError): - permission_create("wiki", "main") + permission_create("wiki.main") -def test_remove_bad_permission(): - # Remove not existant permission +def test_permission_delete_doesnt_existing(): with pytest.raises(MoulinetteError): - permission_delete("non_exit", "main", force=True) + permission_delete("doesnt.exist", force=True) res = user_permission_list()['permissions'] - assert "wiki" in res - assert "main" in res['wiki'] - assert "blog" in res - assert "main" in res['blog'] - assert "mail" in res - assert "main" in res['mail'] - assert "xmpp" in res - assert "main" in res['xmpp'] + assert "wiki.main" in res + assert "blog.main" in res + assert "mail.main" in res + assert "xmpp.main" in res -def test_remove_main_permission(): +def test_permission_delete_main_without_force(): with pytest.raises(YunohostError): - permission_delete("blog", "main") + permission_delete("blog.main") res = user_permission_list()['permissions'] - assert "mail" in res - assert "main" in res['mail'] + assert "blog.main" in res # # Update functions @@ -245,137 +233,100 @@ def test_remove_main_permission(): # user side functions -def test_allow_first_group(): - # Remove permission to all_users and define per users - user_permission_add(["wiki"], "main", group="alice") +def test_permission_add_group(): + user_permission_update("wiki.main", add="alice") - res = user_permission_list()['permissions'] - assert ['alice'] == res['wiki']['main']['allowed_users'] - assert ['alice'] == res['wiki']['main']['allowed_groups'] + res = user_permission_list(full=True)['permissions'] + assert set(res['wiki.main']['allowed']) == set(["all_users", "alice"]) + assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"]) -def test_allow_other_group(): - # Allow new user in a permission - user_permission_add(["blog"], "main", group="bob") +def test_permission_remove_group(): + user_permission_update("blog.main", remove="alice") - res = user_permission_list()['permissions'] - assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) - assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_groups']) + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == [] + assert res['blog.main']['corresponding_users'] == [] -def test_disallow_group_1(): - # Disallow a user in a permission - user_permission_remove(["blog"], "main", group="alice") +def test_permission_add_and_remove_group(): + user_permission_update("wiki.main", add="alice", remove="all_users") - res = user_permission_list()['permissions'] - assert [] == res['blog']['main']['allowed_users'] - assert [] == res['blog']['main']['allowed_groups'] + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['allowed'] == ["alice"] + assert res['wiki.main']['corresponding_users'] == ["alice"] -def test_allow_group_1(): - # Allow a user when he is already allowed - user_permission_add(["blog"], "main", group="alice") +def test_permission_add_group_already_allowed(): + user_permission_update("blog.main", add="alice") - res = user_permission_list()['permissions'] - assert ["alice"] == res['blog']['main']['allowed_users'] - assert ["alice"] == res['blog']['main']['allowed_groups'] + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["alice"] + assert res['blog.main']['corresponding_users'] == ["alice"] -def test_disallow_group_1(): - # Disallow a user when he is already disallowed - user_permission_remove(["blog"], "main", group="bob") +def test_permission_remove_group_already_not_allowed(): + user_permission_update("blog.main", remove="bob") - res = user_permission_list()['permissions'] - assert ["alice"] == res['blog']['main']['allowed_users'] - assert ["alice"] == res['blog']['main']['allowed_groups'] + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["alice"] + assert res['blog.main']['corresponding_users'] == ["alice"] -def test_reset_permission(): +def test_permission_reset(): # Reset permission - user_permission_clear(["blog"], "main") + user_permission_reset("blog.main") - res = user_permission_list()['permissions'] - assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) - assert ["all_users"] == res['blog']['main']['allowed_groups'] - -# internal functions - -def test_add_url_1(): - # Add URL in permission which hasn't any URL defined - permission_update("blog", "main", add_url=[maindomain + "/testA"]) - - res = user_permission_list()['permissions'] - assert [maindomain + "/testA"] == res['blog']['main']['URL'] - -def test_add_url_2(): - # Add a second URL in a permission - permission_update("wiki", "main", add_url=[maindomain + "/testA"]) - - res = user_permission_list()['permissions'] - assert set([maindomain + "/testA", maindomain + "/wiki"]) == set(res['wiki']['main']['URL']) - -def test_remove_url_1(): - permission_update("wiki", "main", remove_url=[maindomain + "/wiki"]) - - res = user_permission_list()['permissions'] - assert 'URL' not in res['wiki']['main'] - -def test_add_url_3(): - # Add a url already added - permission_update("wiki", "main", add_url=[maindomain + "/wiki"]) - - res = user_permission_list()['permissions'] - assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] - -def test_remove_url_2(): - # Remove a url not added (with a permission which contain some URL) - permission_update("wiki", "main", remove_url=[maindomain + "/not_exist"]) - - res = user_permission_list()['permissions'] - assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] - -def test_remove_url_2(): - # Remove a url not added (with a permission which contain no URL) - permission_update("blog", "main", remove_url=[maindomain + "/not_exist"]) - - res = user_permission_list()['permissions'] - assert 'URL' not in res['blog']['main'] + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["all_users"] + assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"]) # # Error on update function # -def test_disallow_bad_group_1(): - # Disallow a group when the group all_users is allowed +def test_permission_add_group_that_doesnt_exist(): with pytest.raises(YunohostError): - user_permission_remove("wiki", "main", group="alice") + user_permission_update("blog.main", add="doesnt_exist") - res = user_permission_list()['permissions'] - assert ["all_users"] == res['wiki']['main']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) + res = user_permission_list(full=True)['permissions'] + assert res['blog.main']['allowed'] == ["alice"] + assert res['blog.main']['corresponding_users'] == ["alice"] -def test_allow_bad_user(): - # Allow a non existant group +def test_permission_update_permission_that_doesnt_exist(): with pytest.raises(YunohostError): - user_permission_add(["blog"], "main", group="not_exist") + user_permission_update("doesnt.exist", add="alice") - res = user_permission_list()['permissions'] - assert ["alice"] == res['blog']['main']['allowed_groups'] - assert ["alice"] == res['blog']['main']['allowed_users'] -def test_disallow_bad_group_2(): - # Disallow a non existant group - with pytest.raises(YunohostError): - user_permission_remove(["blog"], "main", group="not_exist") +# Permission url management - res = user_permission_list()['permissions'] - assert ["alice"] == res['blog']['main']['allowed_groups'] - assert ["alice"] == res['blog']['main']['allowed_users'] +def test_permission_add_url(): + permission_urls("blog.main", add=[maindomain + "/testA"]) -def test_allow_bad_permission_1(): - # Allow a user to a non existant permission - with pytest.raises(YunohostError): - user_permission_add(["wiki"], "not_exit", group="alice") + res = user_permission_list(full=True)['permissions'] + assert res["blog.main"]["urls"] == [maindomain + "/testA"] -def test_allow_bad_permission_2(): - # Allow a user to a non existant permission - with pytest.raises(YunohostError): - user_permission_add(["not_exit"], "main", group="alice") +def test_permission_add_second_url(): + permission_urls("wiki.main", add=[maindomain + "/testA"]) + + res = user_permission_list(full=True)['permissions'] + assert set(res["wiki.main"]["urls"]) == set([maindomain + "/testA", maindomain + "/wiki"]) + +def test_permission_remove_url(): + permission_urls("wiki.main", remove=[maindomain + "/wiki"]) + + res = user_permission_list(full=True)['permissions'] + assert res["wiki.main"]["urls"] == [] + +def test_permission_add_url_already_added(): + res = user_permission_list(full=True)['permissions'] + assert res["wiki.main"]["urls"] == [maindomain + "/wiki"] + + permission_urls("wiki.main", add=[maindomain + "/wiki"]) + + res = user_permission_list(full=True)['permissions'] + assert res["wiki.main"]["urls"] == [maindomain + "/wiki"] + +def test_permission_remove_url_not_added(): + permission_urls("wiki.main", remove=[maindomain + "/doesnt_exist"]) + + res = user_permission_list(full=True)['permissions'] + assert res['wiki.main']['urls'] == [maindomain + "/wiki"] # # Application interaction @@ -385,42 +336,44 @@ def test_install_app(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) - res = user_permission_list()['permissions'] - assert "permissions_app" in res - assert "main" in res['permissions_app'] - assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL'] - assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL'] - assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL'] + res = user_permission_list(full=True)['permissions'] + assert "permissions_app.main" in res + assert "permissions_app.admin" in res + assert "permissions_app.dev" in res + assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] - assert ["all_users"] == res['permissions_app']['main']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['permissions_app']['main']['allowed_users']) + assert res['permissions_app.main']['allowed'] == ["all_users"] + assert set(res['permissions_app.main']['corresponding_users']) == set(["alice", "bob"]) - assert ["alice"] == res['permissions_app']['admin']['allowed_groups'] - assert ["alice"] == res['permissions_app']['admin']['allowed_users'] + assert res['permissions_app.admin']['allowed'] == ["alice"] + assert res['permissions_app.admin']['corresponding_users'] == ["alice"] - assert ["all_users"] == res['permissions_app']['dev']['allowed_groups'] - assert set(["alice", "bob"]) == set(res['permissions_app']['dev']['allowed_users']) + assert res['permissions_app.dev']['allowed'] == [] + assert set(res['permissions_app.dev']['corresponding_users']) == set() def test_remove_app(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) app_remove("permissions_app") - res = user_permission_list()['permissions'] - assert "permissions_app" not in res + # Check all permissions for this app got deleted + res = user_permission_list(full=True)['permissions'] + assert not any(p.startswith("permissions_app.") for p in res.keys()) def test_change_url(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) - res = user_permission_list()['permissions'] - assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL'] - assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL'] - assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL'] + res = user_permission_list(full=True)['permissions'] + assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] app_change_url("permissions_app", maindomain, "/newchangeurl") - res = user_permission_list()['permissions'] - assert [maindomain + "/newchangeurl"] == res['permissions_app']['main']['URL'] - assert [maindomain + "/newchangeurl/admin"] == res['permissions_app']['admin']['URL'] - assert [maindomain + "/newchangeurl/dev"] == res['permissions_app']['dev']['URL'] + res = user_permission_list(full=True)['permissions'] + assert res['permissions_app.main']['urls'] == [maindomain + "/newchangeurl"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/newchangeurl/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/newchangeurl/dev"] From 2e14834e6b98ed29277c57d8f2e4ffce88b04cd8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 17:50:52 +0200 Subject: [PATCH 29/92] Misc fixes following tests --- locales/en.json | 10 +++++----- src/yunohost/app.py | 6 ++---- src/yunohost/permission.py | 6 +++--- src/yunohost/user.py | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index c370f821e..7a16ebd0c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -274,9 +274,9 @@ "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the ip associated with your YunoHost subdomain '{}'", "log_letsencrypt_cert_install": "Install Let's encrypt certificate on '{}' domain", - "log_permission_create": "Create permission '{permission}'", - "log_permission_delete": "Delete permission '{permission}'", - "log_permission_urls": "Update urls related to permission '{permission}'", + "log_permission_create": "Create permission '{}'", + "log_permission_delete": "Delete permission '{}'", + "log_permission_urls": "Update urls related to permission '{}'", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_regen_conf": "Regenerate system configurations '{}'", @@ -286,8 +286,8 @@ "log_user_group_delete": "Delete '{}' group", "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", - "log_user_permission_update": "Update accesses for permission '{permission}'", - "log_user_permission_reset": "Reset permission '{permission}'", + "log_user_permission_update": "Update accesses for permission '{}'", + "log_user_permission_reset": "Reset permission '{}'", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_postinstall": "Postinstall your YunoHost server", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f505dd088..b3c36d059 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -735,11 +735,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if packages.dpkg_is_broken(): raise YunohostError("dpkg_is_broken") - from yunohost.utils.ldap import _get_ldap_interface from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import permission_create, permission_urls, permission_delete, permission_sync_to_user - ldap = _get_ldap_interface() + from yunohost.permission import user_permission_list, permission_create, permission_urls, permission_delete, permission_sync_to_user # Fetch or extract sources if not os.path.exists(INSTALL_TMP): @@ -976,7 +974,7 @@ def app_remove(operation_logger, app): """ from yunohost.hook import hook_exec, hook_remove, hook_callback - from yunohost.permission import permission_delete, permission_sync_to_user + from yunohost.permission import user_permission_list, permission_delete, permission_sync_to_user if not _is_installed(app): raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 4d935d3c0..e5035b0ad 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -45,7 +45,7 @@ SYSTEM_PERMS = ["mail", "xmpp", "stfp"] # -def user_permission_list(short=False, full=False, ignore_system_perms=True): +def user_permission_list(short=False, full=False, ignore_system_perms=False): """ List permissions and corresponding accesses """ @@ -273,13 +273,13 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): attr_dict = { 'objectClass': ['top', 'permissionYnh', 'posixGroup'], - 'cn': permission, + 'cn': str(permission), 'gidNumber': gid, } # For main permission, we add all users by default if permission.endswith(".main"): - attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' + attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org'] if urls: attr_dict['URL'] = [_normalize_url(url) for url in urls] diff --git a/src/yunohost/user.py b/src/yunohost/user.py index ef2a7d523..bb4d6aed2 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -449,7 +449,7 @@ def user_info(username): if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) - elif username not in user_permission_list()["permissions"]["mail.main"]["allowed_users"]: + elif username not in user_permission_list(full=True)["permissions"]["mail.main"]["corresponding_users"]: logger.warning(m18n.n('mailbox_disabled', user=username)) else: cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] From bdad4ffd7114602aea0303793ebdc912da054905 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Sep 2019 18:34:17 +0200 Subject: [PATCH 30/92] c.f. issue 1405 ... those 'if ldap.stuff()' are complete bullshit from the very beginning since they never return False : instead they trigger an exception which means the current error management is completely meaningless ... so this refactorize all the places if found those + add proper error messages --- locales/en.json | 22 ++++---- src/yunohost/domain.py | 16 ++++-- src/yunohost/permission.py | 109 +++++++++++++++++++++---------------- src/yunohost/user.py | 109 ++++++++++++++++++++----------------- 4 files changed, 144 insertions(+), 112 deletions(-) diff --git a/locales/en.json b/locales/en.json index 7a16ebd0c..d60a432ab 100644 --- a/locales/en.json +++ b/locales/en.json @@ -164,9 +164,9 @@ "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Unable to generate certificate", "domain_created": "The domain has been created", - "domain_creation_failed": "Unable to create domain", + "domain_creation_failed": "Failed to create domain {domain}: {error}", "domain_deleted": "The domain has been deleted", - "domain_deletion_failed": "Unable to delete domain", + "domain_deletion_failed": "Failed to delete domain {domain}: {error}", "domain_dns_conf_is_just_a_recommendation": "This command shows you what is the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", "domain_dyndns_already_subscribed": "You've already subscribed to a DynDNS domain", "domain_dyndns_dynette_is_unreachable": "Unable to reach YunoHost dynette, either your YunoHost is not correctly connected to the internet or the dynette server is down. Error: {error}", @@ -229,14 +229,14 @@ "group_already_exist": "Group {group} already exist", "group_already_exist_on_system": "Group {group} already exists in the system group", "group_created": "Group '{group}' successfully created", - "group_creation_failed": "Group creation failed for group '{group}'", + "group_creation_failed": "Failed to create group {group}: {error}", "group_cannot_be_edited": "The group {group} cannot be edited manually.", "group_cannot_be_deleted": "The group {group} cannot be deleted manually.", "group_deleted": "Group '{group}' deleted", - "group_deletion_failed": "Group '{group} 'deletion failed", + "group_deletion_failed": "Failed to delete group {group}: {error}", "group_unknown": "Group {group} unknown", "group_updated": "Group '{group}' updated", - "group_update_failed": "Group update failed for group '{group}'", + "group_update_failed": "Failed to update group {group}: {error}", "group_user_already_in_group": "User {user} is already in group {group}", "group_user_not_in_group": "User {user} is not in group {group}", "hook_exec_failed": "Script execution failed: {path:s}", @@ -430,11 +430,11 @@ "permission_already_exist": "Permission '{permission}' already exists", "permission_cannot_remove_main": "Removing a main permission is not allowed", "permission_created": "Permission '{permission}' created", - "permission_creation_failed": "Failed to create permission '{permission}'", + "permission_creation_failed": "Failed to create permission '{permission}': {error}", "permission_deleted": "Permission '{permission}' deleted", - "permission_deletion_failed": "Failed to delete permission '{permission}'", + "permission_deletion_failed": "Failed to delete permission '{permission}': {error}", "permission_not_found": "Permission '{permission}' does not seem to exist ?", - "permission_update_failed": "Failed to update permission '{permission}'", + "permission_update_failed": "Failed to update permission '{permission}' : {error}", "permission_updated": "Permission '{permission}' updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", @@ -556,13 +556,13 @@ "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", "user_created": "The user has been created", - "user_creation_failed": "Unable to create user", + "user_creation_failed": "Unable to create user {user}: {error}", "user_deleted": "The user has been deleted", - "user_deletion_failed": "Unable to delete user", + "user_deletion_failed": "Unable to delete user {user}: {error}", "user_home_creation_failed": "Unable to create user home folder", "user_info_failed": "Unable to retrieve user information", "user_unknown": "Unknown user: {user:s}", - "user_update_failed": "Unable to update user", + "user_update_failed": "Unable to update user {user}: {error}", "user_updated": "The user has been updated", "users_available": "Available users:", "yunohost_already_installed": "YunoHost is already installed", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 42a4881ba..3f906748b 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -112,8 +112,10 @@ def domain_add(operation_logger, domain, dyndns=False): 'virtualdomain': domain, } - if not ldap.add('virtualdomain=%s,ou=domains' % domain, attr_dict): - raise YunohostError('domain_creation_failed') + try: + ldap.add('virtualdomain=%s,ou=domains' % domain, attr_dict) + except Exception as e: + raise YunohostError('domain_creation_failed', domain=domain, error=e) # Don't regen these conf if we're still in postinstall if os.path.exists('/etc/yunohost/installed'): @@ -167,10 +169,12 @@ def domain_remove(operation_logger, domain, force=False): operation_logger.start() ldap = _get_ldap_interface() - if ldap.remove('virtualdomain=' + domain + ',ou=domains') or force: - os.system('rm -rf /etc/yunohost/certs/%s' % domain) - else: - raise YunohostError('domain_deletion_failed') + try: + ldap.remove('virtualdomain=' + domain + ',ou=domains') + except Exception as e: + raise YunohostError('domain_deletion_failed', domain=domain, error=e) + + os.system('rm -rf /etc/yunohost/certs/%s' % domain) regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) app_ssowatconf() diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index e5035b0ad..984a5d902 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -153,36 +153,37 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, operation_logger.start() - if ldap.update('cn=%s,ou=permission' % permission, - {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}): - logger.debug(m18n.n('permission_updated', permission=permission)) + try: + ldap.update('cn=%s,ou=permission' % permission, + {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]}) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission, error=e) - # Trigger permission sync if asked + logger.debug(m18n.n('permission_updated', permission=permission)) - if sync_perm: - permission_sync_to_user() + # Trigger permission sync if asked - new_permission = user_permission_list(full=True)["permissions"][permission] + if sync_perm: + permission_sync_to_user() - # Trigger app callbacks + new_permission = user_permission_list(full=True)["permissions"][permission] - app = permission.split(".")[0] + # Trigger app callbacks - old_allowed_users = set(existing_permission["corresponding_users"]) - new_allowed_users = set(new_permission["corresponding_users"]) + app = permission.split(".")[0] - effectively_added_users = new_allowed_users - old_allowed_users - effectively_removed_users = old_allowed_users - new_allowed_users + old_allowed_users = set(existing_permission["corresponding_users"]) + new_allowed_users = set(new_permission["corresponding_users"]) - if effectively_added_users: - hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) - if effectively_removed_users: - hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) + effectively_added_users = new_allowed_users - old_allowed_users + effectively_removed_users = old_allowed_users - new_allowed_users - return new_permission + if effectively_added_users: + hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)]) + if effectively_removed_users: + hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)]) - else: - raise YunohostError('permission_update_failed', permission=permission) + return new_permission @is_unit_operation() @@ -209,10 +210,12 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): operation_logger.start() default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} - if ldap.update('cn=%s,ou=permission' % permission, default_permission): - logger.debug(m18n.n('permission_updated', permission=permission)) - else: - raise YunohostError('permission_update_failed', permission=permission) + try: + ldap.update('cn=%s,ou=permission' % permission, default_permission) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission, error=e) + + logger.debug(m18n.n('permission_updated', permission=permission)) if sync_perm: permission_sync_to_user() @@ -286,13 +289,17 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() - if ldap.add('cn=%s,ou=permission' % permission, attr_dict): - if sync_perm: - permission_sync_to_user() - logger.debug(m18n.n('permission_created', permission=permission)) - return user_permission_list(full=True)["permissions"][permission] - else: - raise YunohostError('permission_creation_failed') + + try: + ldap.add('cn=%s,ou=permission' % permission, attr_dict) + except Exception as e: + raise YunohostError('permission_creation_failed', permission=permission, error=e) + + if sync_perm: + permission_sync_to_user() + + logger.debug(m18n.n('permission_created', permission=permission)) + return user_permission_list(full=True)["permissions"][permission] @is_unit_operation() @@ -336,13 +343,17 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() - if ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}): - if sync_perm: - permission_sync_to_user() - logger.debug(m18n.n('permission_updated', permission=permission)) - return user_permission_list(full=True)["permissions"][permission] - else: - raise YunohostError('permission_update_failed', permission=permission) + + try: + ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls}) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission, error=e) + + if sync_perm: + permission_sync_to_user() + + logger.debug(m18n.n('permission_updated', permission=permission)) + return user_permission_list(full=True)["permissions"][permission] @is_unit_operation() @@ -370,12 +381,15 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) operation_logger.related_to.append(('app', permission.split(".")[0])) operation_logger.start() - if ldap.remove('cn=%s,ou=permission' % permission): - if sync_perm: - permission_sync_to_user() - logger.debug(m18n.n('permission_deleted', permission=permission)) - else: - raise YunohostError('permission_deletion_failed', permission=permission) + + try: + ldap.remove('cn=%s,ou=permission' % permission) + except Exception as e: + raise YunohostError('permission_deletion_failed', permission=permission, error=e) + + if sync_perm: + permission_sync_to_user() + logger.debug(m18n.n('permission_deleted', permission=permission)) def permission_sync_to_user(): @@ -410,8 +424,10 @@ def permission_sync_to_user(): 'memberUid': should_be_allowed_users} # Commit the change with the new inherited stuff - if not ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms): - raise YunohostError('permission_update_failed', permission=permission_name) + try: + ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms) + except Exception as e: + raise YunohostError('permission_update_failed', permission=permission_name, error=e) logger.debug("The permission database has been resynchronized") @@ -421,6 +437,7 @@ def permission_sync_to_user(): os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') + def _normalize_url(url): from yunohost.domain import _normalize_domain_path domain = url[:url.index('/')] diff --git a/src/yunohost/user.py b/src/yunohost/user.py index bb4d6aed2..cfb34e44e 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -205,32 +205,34 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, except IOError as e: raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror) - if ldap.add('uid=%s,ou=users' % username, attr_dict): - # Invalidate passwd to take user creation into account - subprocess.call(['nscd', '-i', 'passwd']) + try: + ldap.add('uid=%s,ou=users' % username, attr_dict) + except Exception as e: + raise YunohostError('user_creation_failed', user=username, error=e) - try: - # Attempt to create user home folder - subprocess.check_call( - ['su', '-', username, '-c', "''"]) - except subprocess.CalledProcessError: - if not os.path.isdir('/home/{0}'.format(username)): - logger.warning(m18n.n('user_home_creation_failed'), - exc_info=1) + # Invalidate passwd to take user creation into account + subprocess.call(['nscd', '-i', 'passwd']) - # Create group for user and add to group 'all_users' - user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False) - user_group_update(groupname='all_users', add=username, force=True, sync_perm=True) + try: + # Attempt to create user home folder + subprocess.check_call( + ['su', '-', username, '-c', "''"]) + except subprocess.CalledProcessError: + if not os.path.isdir('/home/{0}'.format(username)): + logger.warning(m18n.n('user_home_creation_failed'), + exc_info=1) - # TODO: Send a welcome mail to user - logger.success(m18n.n('user_created')) + # Create group for user and add to group 'all_users' + user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False) + user_group_update(groupname='all_users', add=username, force=True, sync_perm=True) - hook_callback('post_user_create', - args=[username, mail, password, firstname, lastname]) + # TODO: Send a welcome mail to user + logger.success(m18n.n('user_created')) - return {'fullname': fullname, 'username': username, 'mail': mail} + hook_callback('post_user_create', + args=[username, mail, password, firstname, lastname]) - raise YunohostError('user_creation_failed') + return {'fullname': fullname, 'username': username, 'mail': mail} @is_unit_operation([('username', 'user')]) @@ -258,15 +260,17 @@ def user_delete(operation_logger, username, purge=False): user_group_delete(username, force=True, sync_perm=True) ldap = _get_ldap_interface() - if ldap.remove('uid=%s,ou=users' % username): - # Invalidate passwd to take user deletion into account - subprocess.call(['nscd', '-i', 'passwd']) + try: + ldap.remove('uid=%s,ou=users' % username) + except Exception as e: + raise YunohostError('user_deletion_failed', user=username, error=e) - if purge: - subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) - subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) - else: - raise YunohostError('user_deletion_failed') + # Invalidate passwd to take user deletion into account + subprocess.call(['nscd', '-i', 'passwd']) + + if purge: + subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) + subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) hook_callback('post_user_delete', args=[username, purge]) @@ -387,12 +391,14 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail= operation_logger.start() - if ldap.update('uid=%s,ou=users' % username, new_attr_dict): - logger.success(m18n.n('user_updated')) - app_ssowatconf() - return user_info(username) - else: - raise YunohostError('user_update_failed') + try: + ldap.update('uid=%s,ou=users' % username, new_attr_dict) + except Exception as e: + raise YunohostError('user_update_failed', user=username, error=e) + + logger.success(m18n.n('user_updated')) + app_ssowatconf() + return user_info(username) def user_info(username): @@ -476,10 +482,7 @@ def user_info(username): 'use': storage_use } - if result: - return result_dict - else: - raise YunohostError('user_info_failed') + return result_dict # @@ -569,13 +572,16 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False attr_dict["member"] = ["uid=" + groupname + ",ou=users,dc=yunohost,dc=org"] operation_logger.start() - if ldap.add('cn=%s,ou=groups' % groupname, attr_dict): - logger.success(m18n.n('group_created', group=groupname)) - if sync_perm: - permission_sync_to_user() - return {'name': groupname} + try: + ldap.add('cn=%s,ou=groups' % groupname, attr_dict) + except Exception as e: + raise YunohostError('group_creation_failed', group=groupname, error=e) - raise YunohostError('group_creation_failed', group=groupname) + if sync_perm: + permission_sync_to_user() + + logger.success(m18n.n('group_created', group=groupname)) + return {'name': groupname} @is_unit_operation([('groupname', 'group')]) @@ -601,13 +607,16 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): operation_logger.start() ldap = _get_ldap_interface() - if not ldap.remove('cn=%s,ou=groups' % groupname): - raise YunohostError('group_deletion_failed', group=groupname) + try: + ldap.remove('cn=%s,ou=groups' % groupname) + except Exception as e: + raise YunohostError('group_deletion_failed', group=groupname, error=e) - logger.success(m18n.n('group_deleted', group=groupname)) if sync_perm: permission_sync_to_user() + logger.success(m18n.n('group_deleted', group=groupname)) + @is_unit_operation([('groupname', 'group')]) def user_group_update(operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True): @@ -668,8 +677,10 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= if set(new_group) != set(current_group): operation_logger.start() ldap = _get_ldap_interface() - if not ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}): - raise YunohostError('group_update_failed', group=groupname) + try: + ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)}) + except Exception as e: + raise YunohostError('group_update_failed', group=groupname, error=e) logger.success(m18n.n('group_updated', group=groupname)) if sync_perm: From ec5069b71cd4765c028c593c3cd02da236e0ae2c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 15:35:27 +0200 Subject: [PATCH 31/92] Propagate changes on backup tests + fixes bugs found in the process --- data/hooks/restore/21-conf_ynh_certs | 1 - src/yunohost/backup.py | 15 ++++++------ .../0011_setup_group_permission.py | 5 ++++ src/yunohost/tests/test_backuprestore.py | 23 +++++++++---------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/data/hooks/restore/21-conf_ynh_certs b/data/hooks/restore/21-conf_ynh_certs index d1eb532ed..34e651319 100644 --- a/data/hooks/restore/21-conf_ynh_certs +++ b/data/hooks/restore/21-conf_ynh_certs @@ -3,6 +3,5 @@ backup_dir="$1/conf/ynh/certs" sudo mkdir -p /etc/yunohost/certs/ sudo cp -a $backup_dir/. /etc/yunohost/certs/ -sudo yunohost app ssowatconf sudo service nginx reload sudo service metronome reload diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index f1ac7ee9c..de2b3f76d 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1134,6 +1134,8 @@ class RestoreManager(): self._restore_system() self._restore_apps() + except Exception as e: + logger.error("The following critical error happened during restoration: %s" % e) finally: self.clean() @@ -1186,11 +1188,12 @@ class RestoreManager(): if system_targets == []: return - from yunohost.permission import permission_create, user_permission_update, user_permission_list + from yunohost.user import user_group_list + from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list # Backup old permission for apps # We need to do that because in case of an app is installed we can't remove the permission for this app - old_apps_permission = user_permission_list(ignore_system_perms=True)["permissions"] + old_apps_permission = user_permission_list(ignore_system_perms=True, full=True)["permissions"] # Start register change on system operation_logger = OperationLogger('backup_restore_system') @@ -1232,7 +1235,7 @@ class RestoreManager(): # do the migration 0011 : setup group and permission # # Legacy code - if not user_group_list["groups"]: + if not "all_users" in user_group_list()["groups"].keys(): from yunohost.tools import _get_migration_by_name setup_group_permission = _get_migration_by_name("setup_group_permission") # Update LDAP schema restart slapd @@ -1251,14 +1254,12 @@ class RestoreManager(): permission_create(permission_name, urls=permission_infos["urls"], sync_perm=False) user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"]) - def _restore_apps(self): """Restore all apps targeted""" apps_targets = self.targets.list("apps", exclude=["Skipped"]) for app in apps_targets: - print(app) self._restore_app(app) def _restore_app(self, app_instance_name): @@ -1359,11 +1360,11 @@ class RestoreManager(): permissions = read_yaml('%s/permissions.yml' % app_settings_new_path) existing_groups = user_group_list()['groups'] - for permission_name, permission_infos in permissions: + for permission_name, permission_infos in permissions.items(): permission_create(permission_name, urls=permission_infos.get("urls", [])) - if "allowed" not in permissions_infos: + if "allowed" not in permission_infos: logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s ... You might need to reconfigure permissions yourself!" % (permission_name, app_instance_name)) else: groups = [g for g in permission_infos["allowed"] if g in existing_groups] diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 720e4ac36..109757bcc 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -89,6 +89,11 @@ class MyMigration(Migration): app_setting(app, 'allowed_users', delete=True) def run(self): + + # FIXME : what do we really want to do here ... + # Imho we should just force-regen the conf in all case, and maybe + # just display a warning if we detect that the conf was manually modified + # Check if the migration can be processed ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) # By this we check if the have been customized diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 7d384a46a..d2fd03799 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -38,10 +38,10 @@ def setup_function(function): add_archive_wordpress_from_2p4() assert len(backup_list()["archives"]) == 1 - if "with_backup_legacy_app_installed" in markers: - assert not app_is_installed("backup_legacy_app") - install_app("backup_legacy_app_ynh", "/yolo") - assert app_is_installed("backup_legacy_app") + if "with_legacy_app_installed" in markers: + assert not app_is_installed("legacy_app") + install_app("legacy_app_ynh", "/yolo") + assert app_is_installed("legacy_app") if "with_backup_recommended_app_installed" in markers: assert not app_is_installed("backup_recommended_app") @@ -105,7 +105,7 @@ def backup_test_dependencies_are_met(): # Dummy test apps (or backup archives) assert os.path.exists("./tests/apps/backup_wordpress_from_2p4") - assert os.path.exists("./tests/apps/backup_legacy_app_ynh") + assert os.path.exists("./tests/apps/legacy_app_ynh") assert os.path.exists("./tests/apps/backup_recommended_app_ynh") return True @@ -155,8 +155,8 @@ def delete_all_backups(): def uninstall_test_apps_if_needed(): - if _is_installed("backup_legacy_app"): - app_remove("backup_legacy_app") + if _is_installed("legacy_app"): + app_remove("legacy_app") if _is_installed("backup_recommended_app"): app_remove("backup_recommended_app") @@ -497,10 +497,10 @@ def test_restore_app_already_installed(mocker): assert _is_installed("wordpress") -@pytest.mark.with_backup_legacy_app_installed +@pytest.mark.with_legacy_app_installed def test_backup_and_restore_legacy_app(): - _test_backup_and_restore_app("backup_legacy_app") + _test_backup_and_restore_app("legacy_app") @pytest.mark.with_backup_recommended_app_installed @@ -531,7 +531,7 @@ def _test_backup_and_restore_app(app): # Uninstall the app app_remove(app) assert not app_is_installed(app) - assert app not in user_permission_list()['permissions'] + assert app+".main" not in user_permission_list()['permissions'] # Restore the app backup_restore(system=None, name=archives[0], @@ -541,8 +541,7 @@ def _test_backup_and_restore_app(app): # Check permission per_list = user_permission_list()['permissions'] - assert app in per_list - assert "main" in per_list[app] + assert app+".main" in per_list # # Some edge cases # From ccc7583ec4e909e4c2d06511f32310e6d3abba6c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 16:02:02 +0200 Subject: [PATCH 32/92] Add backup/restore test for permission app, and fix a small related bug --- src/yunohost/backup.py | 6 ++- src/yunohost/tests/test_backuprestore.py | 55 ++++++++++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index de2b3f76d..420c2d4f8 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1367,8 +1367,10 @@ class RestoreManager(): if "allowed" not in permission_infos: logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s ... You might need to reconfigure permissions yourself!" % (permission_name, app_instance_name)) else: - groups = [g for g in permission_infos["allowed"] if g in existing_groups] - user_permission_update(permission_name, remove="all_users", add=groups) + should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups] + current_allowed = user_permission_list()["permissions"][permission_name]["allowed"] + if should_be_allowed != current_allowed: + user_permission_update(permission_name, remove=current_allowed, add=should_be_allowed) os.remove('%s/permissions.yml' % app_settings_new_path) else: diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index d2fd03799..bdaf25299 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -10,7 +10,7 @@ from yunohost.app import _is_installed from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError -from yunohost.user import user_permission_list +from yunohost.user import user_permission_list, user_create, user_list, user_delete from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps # Get main domain @@ -59,6 +59,13 @@ def setup_function(function): add_archive_system_from_2p4() assert len(backup_list()["archives"]) == 1 + if "with_permission_app_installed" in markers: + assert not app_is_installed("permissions_app") + user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") + install_app("permissions_app_ynh", "/urlpermissionapp" + "&admin=alice") + assert app_is_installed("permissions_app") + def teardown_function(function): @@ -73,6 +80,9 @@ def teardown_function(function): if "clean_opt_dir" in markers: shutil.rmtree("/opt/test_backup_output_directory") + if "alice" in user_list()["users"]: + user_delete("alice") + @pytest.fixture(autouse=True) def check_LDAP_db_integrity_call(): @@ -92,6 +102,9 @@ def check_permission_for_apps_call(): def app_is_installed(app): + if app == "permissions_app": + return _is_installed(app) + # These are files we know should be installed by the app app_files = [] app_files.append("/etc/nginx/conf.d/%s.d/%s.conf" % (maindomain, app)) @@ -155,14 +168,9 @@ def delete_all_backups(): def uninstall_test_apps_if_needed(): - if _is_installed("legacy_app"): - app_remove("legacy_app") - - if _is_installed("backup_recommended_app"): - app_remove("backup_recommended_app") - - if _is_installed("wordpress"): - app_remove("wordpress") + for app in ["legacy_app", "backup_recommended_app", "wordpress", "permissions_app"]: + if _is_installed(app): + app_remove(app) def install_app(app, path, additionnal_args=""): @@ -514,6 +522,35 @@ def test_backup_and_restore_with_ynh_restore(): _test_backup_and_restore_app("backup_recommended_app") +@pytest.mark.with_permission_app_installed +def test_backup_and_restore_permission_app(): + + res = user_permission_list(full=True)['permissions'] + assert "permissions_app.main" in res + assert "permissions_app.admin" in res + assert "permissions_app.dev" in res + assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] + + assert res['permissions_app.main']['allowed'] == ["all_users"] + assert res['permissions_app.admin']['allowed'] == ["alice"] + assert res['permissions_app.dev']['allowed'] == [] + + _test_backup_and_restore_app("permissions_app") + + res = user_permission_list(full=True)['permissions'] + assert "permissions_app.main" in res + assert "permissions_app.admin" in res + assert "permissions_app.dev" in res + assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"] + assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"] + assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"] + + assert res['permissions_app.main']['allowed'] == ["all_users"] + assert res['permissions_app.admin']['allowed'] == ["alice"] + assert res['permissions_app.dev']['allowed'] == [] + def _test_backup_and_restore_app(app): From 302e755f48f853cf1362eb577885c990cc434ca7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 16:50:46 +0200 Subject: [PATCH 33/92] Assume we target the .main permission if it's not given explicitly --- data/actionsmap/yunohost.yml | 4 ++-- src/yunohost/permission.py | 32 ++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 49dde373b..05f0de048 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -298,7 +298,7 @@ user: api: POST /users/permissions/ arguments: permission: - help: Permission to manage (e.g. mail.main or wordpress.editors) + help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) -a: full: --add help: Group or user names to add to this permission @@ -320,7 +320,7 @@ user: api: DELETE /users/permissions/ arguments: permission: - help: Permission to be resetted (e.g. mail.main or wordpress.editors) + help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) ssh: subcategory_help: Manage ssh access diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 984a5d902..1472f4b88 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -87,15 +87,19 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, Allow or Disallow a user or group to a permission for a specific application Keyword argument: - permission -- Name of the permission (e.g. mail.mail or wordpress.editors) + permission -- Name of the permission (e.g. mail or or wordpress or wordpress.editors) add -- List of groups or usernames to add to this permission remove -- List of groups or usernames to remove from to this permission """ from yunohost.hook import hook_callback from yunohost.user import user_group_list - from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract + from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + # Fetch currently allowed groups for this permission existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) @@ -146,7 +150,7 @@ def user_permission_update(operation_logger, permission, add=None, remove=None, # Don't update LDAP if we update exactly the same values if set(new_allowed_groups) == set(current_allowed_groups): # FIXME : i18n - logger.warning("No change was applied because not relevant modification were found") + logger.warning("The permission was not updated all addition/removal requests already match the current state.") return # Commit the new allowed group list @@ -192,12 +196,16 @@ def user_permission_reset(operation_logger, permission, sync_perm=True): Reset a given permission to just 'all_users' Keyword argument: - permission -- The name of the permission to be reseted + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) """ from yunohost.hook import hook_callback from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + # Fetch existing permission existing_permission = user_permission_list(full=True)["permissions"].get(permission, None) @@ -254,13 +262,17 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True): Create a new permission for a specific application Keyword argument: - permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) urls -- list of urls to specify for the permission """ from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + # Validate uniqueness of permission in LDAP if ldap.get_conflict({'cn': permission}, base_dn='ou=permission,dc=yunohost,dc=org'): @@ -308,7 +320,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe Update urls related to a permission for a specific application Keyword argument: - permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) add -- List of urls to add remove -- List of urls to remove @@ -362,10 +374,14 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) Delete a permission Keyword argument: - permission -- Name of the permission (e.g. nextcloud.main or wordpress.editors) + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) """ - if permission.endswith("main") and not force: + # By default, manipulate main permission + if "." not in permission: + permission = permission + ".main" + + if permission.endswith(".main") and not force: raise YunohostError('permission_cannot_remove_main') from yunohost.utils.ldap import _get_ldap_interface From f950378c63aec6a2536524e3e02c3cfda86a09b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 17:39:21 +0200 Subject: [PATCH 34/92] Do not display primary groups by default when running yunohost user group list --- data/actionsmap/yunohost.yml | 5 +++++ src/yunohost/user.py | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 05f0de048..1f6966f65 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -216,6 +216,11 @@ user: full: --full help: Display all informations known about each groups action: store_true + -p: + full: --include-primary-groups + help: Also display primary groups (each user has an eponym group that only contains itself) + action: store_true + default: false ### user_group_create() create: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index cfb34e44e..4fe8db420 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -488,13 +488,17 @@ def user_info(username): # # Group subcategory # -def user_group_list(short=False, full=False): +def user_group_list(short=False, full=False, include_primary_groups=True): """ List users Keyword argument: short -- Only list the name of the groups without any additional info full -- List all the info available for each groups + include_primary_groups -- Include groups corresponding to users (which should always only contains this user) + This option is set to false by default in the action map because we don't want to have + these displayed when the user runs `yunohost user group list`, but internally we do want + to list them when called from other functions """ # Fetch relevant informations @@ -507,10 +511,15 @@ def user_group_list(short=False, full=False): # Parse / organize information to be outputed + users = user_list()["users"] groups = {} for infos in groups_infos: name = infos["cn"][0] + + if not include_primary_groups and name in users: + continue + groups[name] = {} groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])] From ea8c0cae9431bd294f838448eb297d84ee6fd5a4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 18:34:26 +0200 Subject: [PATCH 35/92] Deprecate legacy app access system --- data/actionsmap/yunohost.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 1f6966f65..e51f23a14 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -792,6 +792,7 @@ app: addaccess: action_help: Grant access right to users (everyone by default) api: PUT /access + deprecated: true arguments: apps: nargs: "+" @@ -803,6 +804,7 @@ app: removeaccess: action_help: Revoke access right to users (everyone by default) api: DELETE /access + deprecated: true arguments: apps: nargs: "+" @@ -814,6 +816,7 @@ app: clearaccess: action_help: Reset access rights for the app api: POST /access + deprecated: true arguments: apps: nargs: "+" From b995b3254d95008f51591a76d8bbbcfab7bc260c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 18:41:05 +0200 Subject: [PATCH 36/92] Remove some unecessary messages when handling primary groups and all_users --- src/yunohost/user.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 4fe8db420..22bd6d3cf 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -589,7 +589,11 @@ def user_group_create(operation_logger, groupname, gid=None, primary_group=False if sync_perm: permission_sync_to_user() - logger.success(m18n.n('group_created', group=groupname)) + if not primary_group: + logger.success(m18n.n('group_created', group=groupname)) + else: + logger.debug(m18n.n('group_created', group=groupname)) + return {'name': groupname} @@ -624,7 +628,10 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): if sync_perm: permission_sync_to_user() - logger.success(m18n.n('group_deleted', group=groupname)) + if groupname not in existing_users: + logger.success(m18n.n('group_deleted', group=groupname)) + else: + logger.debug(m18n.n('group_deleted', group=groupname)) @is_unit_operation([('groupname', 'group')]) @@ -691,7 +698,11 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force= except Exception as e: raise YunohostError('group_update_failed', group=groupname, error=e) - logger.success(m18n.n('group_updated', group=groupname)) + if groupname != "all_users": + logger.success(m18n.n('group_updated', group=groupname)) + else: + logger.debug(m18n.n('group_updated', group=groupname)) + if sync_perm: permission_sync_to_user() return user_group_info(groupname) From 732f8987738bfb585de14a0922d374e7c2c616e4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 19:42:15 +0200 Subject: [PATCH 37/92] Small issue when deleting the user --- src/yunohost/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 22bd6d3cf..fbd15018c 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -253,6 +253,8 @@ def user_delete(operation_logger, username, purge=False): user_group_update("all_users", remove=username, force=True, sync_perm=False) for group, infos in user_group_list()["groups"].items(): + if group == "all_users": + continue # If the user is in this group (and it's not the primary group), # remove the member from the group if username != group and username in infos["members"]: From 63fa54171de033a1fe82612a97511e0579d3eff1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 20:13:44 +0200 Subject: [PATCH 38/92] Ugh we really need to make this raise an exception ... --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 420c2d4f8..305152865 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1135,7 +1135,7 @@ class RestoreManager(): self._restore_system() self._restore_apps() except Exception as e: - logger.error("The following critical error happened during restoration: %s" % e) + raise YunohostError("The following critical error happened during restoration: %s" % e) finally: self.clean() @@ -1245,7 +1245,7 @@ class RestoreManager(): # Remove all permission for all app which is still in the LDAP for permission_name in user_permission_list(ignore_system_perms=True)["permissions"].keys(): - permission_delete(permission_name) + permission_delete(permission_name, force=True) # Restore permission for the app which is installed for permission_name, permission_infos in old_apps_permission.items(): From 3df6ce17b6b13f1fe784a3bb9e5b782fc541eb2a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 20:34:30 +0200 Subject: [PATCH 39/92] Properly handle all those errors >.> ... --- locales/en.json | 1 + src/yunohost/tests/test_permission.py | 3 +-- src/yunohost/tests/test_user-group.py | 17 ++++++++------ src/yunohost/user.py | 33 +++++++++++++++++++++------ 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/locales/en.json b/locales/en.json index d60a432ab..c48532ed7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -555,6 +555,7 @@ "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", + "user_already_exists": "User {user} already exists", "user_created": "The user has been created", "user_creation_failed": "Unable to create user {user}: {error}", "user_deleted": "The user has been deleted", diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 2df6362a9..8db1ae825 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1,6 +1,5 @@ import pytest -from moulinette.core import MoulinetteError from yunohost.app import app_install, app_remove, app_change_url, app_list from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ @@ -211,7 +210,7 @@ def test_permission_create_already_existing(): permission_create("wiki.main") def test_permission_delete_doesnt_existing(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): permission_delete("doesnt.exist", force=True) res = user_permission_list()['permissions'] diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 88644c3e6..30bdeb017 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -1,6 +1,5 @@ import pytest -from moulinette.core import MoulinetteError from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info from yunohost.domain import _get_maindomain @@ -102,19 +101,23 @@ def test_del_group(): # def test_create_user_with_mail_address_already_taken(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh") def test_create_user_with_password_too_simple(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_create("other", "Alice", "White", "other@" + maindomain, "12") def test_create_user_already_exists(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh") +def test_update_user_with_mail_address_already_taken(): + with pytest.raises(YunohostError): + user_update("bob", add_mailalias="alice@" + maindomain) + def test_del_user_that_does_not_exist(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_delete("doesnt_exist") def test_create_group_all_users(): @@ -124,7 +127,7 @@ def test_create_group_all_users(): def test_create_group_already_exists(): # Check groups already exist (regular groups) - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_group_create("dev") def test_del_group_all_users(): @@ -132,7 +135,7 @@ def test_del_group_all_users(): user_group_delete("all_users") def test_del_group_that_does_not_exist(): - with pytest.raises(MoulinetteError): + with pytest.raises(YunohostError): user_group_delete("doesnt_exist") # diff --git a/src/yunohost/user.py b/src/yunohost/user.py index fbd15018c..c6413d7e1 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -127,12 +127,18 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, ldap = _get_ldap_interface() + if username in user_list()["users"]: + raise YunohostError("user_already_exists", user=username) + # Validate uniqueness of username and mail in LDAP - ldap.validate_uniqueness({ - 'uid': username, - 'mail': mail, - 'cn': username - }) + try: + ldap.validate_uniqueness({ + 'uid': username, + 'mail': mail, + 'cn': username + }) + except Exception as e: + raise YunohostError('user_creation_failed', user=username, error=e) # Validate uniqueness of username in system users all_existing_usernames = {x.pw_name for x in pwd.getpwall()} @@ -249,6 +255,9 @@ def user_delete(operation_logger, username, purge=False): from yunohost.utils.ldap import _get_ldap_interface from yunohost.permission import permission_sync_to_user + if username not in user_list()["users"]: + raise YunohostError('user_unknown', user=username) + operation_logger.start() user_group_update("all_users", remove=username, force=True, sync_perm=False) @@ -340,7 +349,10 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail= 'webmaster@' + main_domain, 'postmaster@' + main_domain, ] - ldap.validate_uniqueness({'mail': mail}) + try: + ldap.validate_uniqueness({'mail': mail}) + except Exception as e: + raise YunohostError('user_update_failed', user=username, error=e) if mail[mail.find('@') + 1:] not in domains: raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:]) if mail in aliases: @@ -353,7 +365,10 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail= if not isinstance(add_mailalias, list): add_mailalias = [add_mailalias] for mail in add_mailalias: - ldap.validate_uniqueness({'mail': mail}) + try: + ldap.validate_uniqueness({'mail': mail}) + except Exception as e: + raise YunohostError('user_update_failed', user=username, error=e) if mail[mail.find('@') + 1:] not in domains: raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:]) user['mail'].append(mail) @@ -611,6 +626,10 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): from yunohost.permission import permission_sync_to_user from yunohost.utils.ldap import _get_ldap_interface + existing_groups = user_group_list()['groups'].keys() + if groupname not in existing_groups: + raise YunohostError('group_unknown', group=groupname) + # Refuse to delete primary groups of a user (e.g. group 'sam' related to user 'sam') # without the force option... # From 094a2afe1a7a5eb21c9fdcb51ab79de05c9589a7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Sep 2019 22:45:31 +0200 Subject: [PATCH 40/92] Simplify permission handling in app_map + add tests for it --- src/yunohost/app.py | 10 +++------- src/yunohost/tests/test_permission.py | 15 +++++++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b3c36d059..ab290cb4d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -406,10 +406,10 @@ def app_map(app=None, raw=False, user=None): """ from yunohost.permission import user_permission_list - from yunohost.utils.ldap import _get_ldap_interface apps = [] result = {} + permissions = user_permission_list(full=True)["permissions"] if app is not None: if not _is_installed(app): @@ -429,12 +429,8 @@ def app_map(app=None, raw=False, user=None): continue if 'no_sso' in app_settings: # I don't think we need to check for the value here continue - if user is not None: - ldap = _get_ldap_interface() - if not ldap.search(base='ou=permission,dc=yunohost,dc=org', - filter='(&(objectclass=permissionYnh)(cn=%s.main)(inheritPermission=uid=%s,ou=users,dc=yunohost,dc=org))' % (app_id, user), - attrs=['cn']): - continue + if user and user not in permissions[app_id + ".main"]["corresponding_users"]: + continue domain = app_settings['domain'] path = app_settings['path'] diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 8db1ae825..94728505d 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1,6 +1,6 @@ import pytest -from yunohost.app import app_install, app_remove, app_change_url, app_list +from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \ user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info @@ -331,7 +331,7 @@ def test_permission_remove_url_not_added(): # Application interaction # -def test_install_app(): +def test_permission_app_install(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) @@ -352,7 +352,14 @@ def test_install_app(): assert res['permissions_app.dev']['allowed'] == [] assert set(res['permissions_app.dev']['corresponding_users']) == set() -def test_remove_app(): + # Check that we get the right stuff in app_map, which is used to generate the ssowatconf + assert maindomain + "/urlpermissionapp" in app_map(user="alice").keys() + user_permission_update("permissions_app.main", remove="all_users", add="bob") + assert maindomain + "/urlpermissionapp" not in app_map(user="alice").keys() + assert maindomain + "/urlpermissionapp" in app_map(user="bob").keys() + + +def test_permission_app_remove(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) app_remove("permissions_app") @@ -361,7 +368,7 @@ def test_remove_app(): res = user_permission_list(full=True)['permissions'] assert not any(p.startswith("permissions_app.") for p in res.keys()) -def test_change_url(): +def test_permission_app_change_url(): app_install("./tests/apps/permissions_app_ynh", args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) From 9c383ef06a106505d3f726276d6d2cfba4e4cc41 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 14 Sep 2019 18:21:42 +0200 Subject: [PATCH 41/92] Make migration more robust to re-runs --- locales/en.json | 2 +- .../0011_setup_group_permission.py | 31 ++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index c48532ed7..ae349edf3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -354,7 +354,6 @@ "migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0011_create_group": "Creating a group for each user...", "migration_0011_done": "Migration successful. You are now able to manage groups of users.", - "migration_0011_error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration need to be updated.\nYou need to save your actual configuration, reintialize the original configuration by the command 'yunohost tools regen-conf -f' and after retry the migration", "migration_0011_LDAP_update_failed": "LDAP update failed. Error: {error:s}", "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...", @@ -362,6 +361,7 @@ "migration_0011_rollback_success": "Rollback succeeded.", "migration_0011_update_LDAP_database": "Updating LDAP database...", "migration_0011_update_LDAP_schema": "Updating LDAP schema...", + "migration_0011_failed_to_remove_stale_object": "Failed to remove stale object {dn}: {error}", "migrations_already_ran": "Those migrations have already been ran: {ids}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", "migrations_dependencies_not_satisfied": "Can't run migration {id} because first you need to run these migrations: {dependencies_id}", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 109757bcc..8949239e0 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -28,6 +28,28 @@ class MyMigration(Migration): required = True + def remove_if_exists(self, target): + + from yunohost.utils.ldap import _get_ldap_interface + ldap = _get_ldap_interface() + + try: + objects = ldap.search(target + ",dc=yunohost,dc=org") + # ldap search will raise an exception if no corresponding object is found >.> ... + except Exception as e: + logger.debug("%s does not exist, no need to delete it" % target) + return + + objects.reverse() + for o in objects: + for dn in o["dn"]: + dn = dn.replace(",dc=yunohost,dc=org", "") + logger.debug("Deleting old object %s ..." % dn) + try: + ldap.remove(dn) + except Exception as e: + raise YunohostError("migration_0011_failed_to_remove_stale_object", dn=dn, error=e) + def migrate_LDAP_db(self): logger.info(m18n.n("migration_0011_update_LDAP_database")) @@ -35,14 +57,13 @@ class MyMigration(Migration): from yunohost.utils.ldap import _get_ldap_interface ldap = _get_ldap_interface() - try: - ldap.remove('cn=sftpusers,ou=groups') - except: - logger.warn(m18n.n("migration_0011_error_when_removing_sftpuser_group")) - ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') try: + self.remove_if_exists("cn=sftpusers,ou=groups") + self.remove_if_exists("ou=permission") + self.remove_if_exists('cn=all_users,ou=groups') + attr_dict = ldap_map['parents']['ou=permission'] ldap.add('ou=permission', attr_dict) From eb57a4ad9e982eea6a5dc0c7543c61a437265568 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Sep 2019 19:51:27 +0200 Subject: [PATCH 42/92] Get rid of etckeeper --- data/hooks/conf_regen/01-yunohost | 3 -- data/templates/yunohost/etckeeper.conf | 43 -------------------------- debian/control | 2 +- 3 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 data/templates/yunohost/etckeeper.conf diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index faf041110..f22de7a53 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -53,9 +53,6 @@ do_pre_regen() { else sudo cp services.yml /etc/yunohost/services.yml fi - - mkdir -p "$pending_dir"/etc/etckeeper/ - cp etckeeper.conf "$pending_dir"/etc/etckeeper/ } _update_services() { diff --git a/data/templates/yunohost/etckeeper.conf b/data/templates/yunohost/etckeeper.conf deleted file mode 100644 index 2d11c3dc6..000000000 --- a/data/templates/yunohost/etckeeper.conf +++ /dev/null @@ -1,43 +0,0 @@ -# The VCS to use. -#VCS="hg" -VCS="git" -#VCS="bzr" -#VCS="darcs" - -# Options passed to git commit when run by etckeeper. -GIT_COMMIT_OPTIONS="--quiet" - -# Options passed to hg commit when run by etckeeper. -HG_COMMIT_OPTIONS="" - -# Options passed to bzr commit when run by etckeeper. -BZR_COMMIT_OPTIONS="" - -# Options passed to darcs record when run by etckeeper. -DARCS_COMMIT_OPTIONS="-a" - -# Uncomment to avoid etckeeper committing existing changes -# to /etc automatically once per day. -#AVOID_DAILY_AUTOCOMMITS=1 - -# Uncomment the following to avoid special file warning -# (the option is enabled automatically by cronjob regardless). -#AVOID_SPECIAL_FILE_WARNING=1 - -# Uncomment to avoid etckeeper committing existing changes to -# /etc before installation. It will cancel the installation, -# so you can commit the changes by hand. -#AVOID_COMMIT_BEFORE_INSTALL=1 - -# The high-level package manager that's being used. -# (apt, pacman-g2, yum, zypper etc) -HIGHLEVEL_PACKAGE_MANAGER=apt - -# The low-level package manager that's being used. -# (dpkg, rpm, pacman, pacman-g2, etc) -LOWLEVEL_PACKAGE_MANAGER=dpkg - -# To push each commit to a remote, put the name of the remote here. -# (eg, "origin" for git). Space-separated lists of multiple remotes -# also work (eg, "origin gitlab github" for git). -PUSH_REMOTE="" diff --git a/debian/control b/debian/control index 64c7cd31d..c0604d90e 100644 --- a/debian/control +++ b/debian/control @@ -31,7 +31,7 @@ Depends: ${python:Depends}, ${misc:Depends} , equivs, lsof Recommends: yunohost-admin , openssh-server, ntp, inetutils-ping | iputils-ping - , bash-completion, rsyslog, etckeeper + , bash-completion, rsyslog , php-gd, php-curl, php-gettext, php-mcrypt , python-pip , unattended-upgrades From 379c28de909774bac44e10af34ecd6d91e281ab1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Sep 2019 15:00:31 +0200 Subject: [PATCH 43/92] Update src/yunohost/backup.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Allan Nordhøy --- src/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 305152865..c28160342 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1365,7 +1365,7 @@ class RestoreManager(): permission_create(permission_name, urls=permission_infos.get("urls", [])) if "allowed" not in permission_infos: - logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s ... You might need to reconfigure permissions yourself!" % (permission_name, app_instance_name)) + logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." % (permission_name, app_instance_name)) else: should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups] current_allowed = user_permission_list()["permissions"][permission_name]["allowed"] From 545f697df2378d138ce7af73ef985df170f445e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 20 Sep 2019 21:56:44 +0200 Subject: [PATCH 44/92] When using the legacy adduser function, remove all_users for backward compatibility --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ab290cb4d..ae6d27b20 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1039,7 +1039,7 @@ def app_addaccess(apps, users=[]): output = {} for app in apps: - permission = user_permission_update(app+".main", add=users) + permission = user_permission_update(app+".main", add=users, remove="all_users") output[app] = permission["corresponding_users"] return {'allowed_users': output} From e2e960175b5a9839cb1c30587a16f7c1d4a44dd2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 21 Sep 2019 13:16:28 +0200 Subject: [PATCH 45/92] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update descriptions for action map Co-Authored-By: Allan Nordhøy --- data/actionsmap/yunohost.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e51f23a14..97489a841 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -270,11 +270,11 @@ user: ### user_group_info() info: - action_help: Get information for a specific group + action_help: Get information about a specific group api: GET /users/groups/ arguments: groupname: - help: Name of the group to get info about + help: Name of the group to fetch info about extra: pattern: *pattern_username @@ -289,11 +289,11 @@ user: arguments: -s: full: --short - help: List only the names of permissions + help: Only list permission names action: store_true -f: full: --full - help: Display all informations known about each permissions, including the full list of users corresponding to allowed groups. + help: Display all info known about each permission, including the full user list of each group it is granted to. action: store_true @@ -306,14 +306,14 @@ user: help: Permission to manage (e.g. mail or nextcloud or wordpress.editors) -a: full: --add - help: Group or user names to add to this permission + help: Group or usernames to grant this permission to nargs: "*" metavar: GROUP_OR_USER extra: pattern: *pattern_username -r: full: --remove - help: Group or user names to remove from this permission + help: Group or usernames revoke this permission from nargs: "*" metavar: GROUP_OR_USER extra: From 50fbfb0372f4478cf373ea67b883cbaa1ba9c6c7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 24 Sep 2019 23:18:05 +0200 Subject: [PATCH 46/92] Fucking ugly workaround for the goddamn dependency nighmare from sury djeezus kraiste --- data/helpers.d/apt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index b4bf60c1f..f5590b38d 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -218,6 +218,27 @@ ynh_install_app_dependencies () { fi local dep_app=${app//_/-} # Replace all '_' by '-' + # + # Epic ugly hack to fix the goddamn dependency nightmare of sury + # Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective + # https://github.com/YunoHost/issues/issues/1407 + # + # If we require to install php dependency + if echo $dependencies | grep -q 'php'; + then + # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33+1 on debian) + if dpkg --list | grep php | grep -q "7.0.33-10" + then + # And sury ain't already installed + if ! grep -nrq "sury" /etc/apt/sources.list* + then + # Re-add sury + echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury.list + wget -O /etc/apt/trusted.gpg.d/sury.gpg https://packages.sury.org/php/apt.gpg + fi + fi + fi + cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build Section: misc Priority: optional From ea9a93cec097a14f043b9ecc295ea23b7d14629a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 25 Sep 2019 00:07:50 +0200 Subject: [PATCH 47/92] Update data/actionsmap/yunohost.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Allan Nordhøy --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 97489a841..22037f05f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -299,7 +299,7 @@ user: ### user_permission_update() update: - action_help: Grant / remove permissions to groups or users + action_help: Manage group or user permissions api: POST /users/permissions/ arguments: permission: From 24cc26d85a35ccd38c5af369e7bd9c739fca441c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Sep 2019 14:23:01 +0200 Subject: [PATCH 48/92] Support logfiles not ending with .log in logrotate ... --- data/helpers.d/logrotate | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 47ce46cf6..82cdee6a5 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -40,10 +40,13 @@ ynh_use_logrotate () { fi if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then - if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile - local logfile=$1 # In this case, focus logrotate on the logfile + # If the given logfile parameter already exists as a file, or if it ends up with ".log", + # we just want to manage a single file + if [ -f "$1" ] || [ "$(echo ${1##*.})" == "log" ]; then + local logfile=$1 + # Otherwise we assume we want to manage a directory and all its .log file inside else - local logfile=$1/*.log # Else, uses the directory and all logfile into it. + local logfile=$1/*.log fi fi # LEGACY CODE @@ -54,7 +57,7 @@ ynh_use_logrotate () { fi if [ -n "$logfile" ] then - if [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile + if [ ! -f "$1" ] && [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it. fi else From c0e3d600b264eab6b5f817a9c83154edda892cd1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Sep 2019 17:17:03 +0200 Subject: [PATCH 49/92] If we got fed an app url, extract the name of the app to test if we do know it --- src/yunohost/app.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5a51e57bb..061113b4b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -797,9 +797,19 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu raw_app_list = app_list(raw=True) if app in raw_app_list or ('@' in app) or ('http://' in app) or ('https://' in app): + + # If we got an app name directly (e.g. just "wordpress"), we gonna test this name if app in raw_app_list: - state = raw_app_list[app].get("state", "notworking") - level = raw_app_list[app].get("level", None) + app_name_to_test = app + # If we got an url like "https://github.com/foo/bar_ynh, we want to + # extract "bar" and test if we know this app + elif ('http://' in app) or ('https://' in app): + app_name_to_test = app.strip("/").split("/")[-1].replace("_ynh","") + + if app_name_to_test in raw_app_list: + + state = raw_app_list[app_name_to_test].get("state", "notworking") + level = raw_app_list[app_name_to_test].get("level", None) confirm = "danger" if state in ["working", "validated"]: if isinstance(level, int) and level >= 3: From a2ecbb9d8bc4e1bd8c08fc6ac4a8bad27e8efacc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Sep 2019 17:38:00 +0200 Subject: [PATCH 50/92] Make the warning spooky for notworking and thirdparty apps ... --- locales/en.json | 4 ++-- src/yunohost/app.py | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 61fdcfa9b..a4ee33abf 100644 --- a/locales/en.json +++ b/locales/en.json @@ -144,8 +144,8 @@ "certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file:s})", "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file:s})", "confirm_app_install_warning": "Warning: This application may work, but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ", - "confirm_app_install_danger": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", - "confirm_app_install_thirdparty": "WARNING! Installing third-party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ", + "confirm_app_install_danger": "DANGER! This application is known to be still experimental (if not explicitly not working)! You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system... If you are willing to take that risk anyway, type '{answers:s}'", + "confirm_app_install_thirdparty": "DANGER! This application is not part of Yunohost's application catalog. Installing third-party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. NO SUPPORT will be provided if this app doesn't work or break your system... If you are willing to take that risk anyway, type '{answers:s}'", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", "custom_appslist_name_required": "You must provide a name for your custom app list", "diagnosis_debian_version_error": "Could not retrieve the Debian version: {error}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 061113b4b..c85a017e4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -789,10 +789,21 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu if confirm is None or force or msettings.get('interface') == 'api': return - answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm, - answers='Y/N')) - if answer.upper() != "Y": - raise YunohostError("aborting") + if confirm in ["danger", "thirdparty"]: + answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm, + answers='Yes, I understand'), + color="red") + if answer != "Yes, I understand": + raise YunohostError("aborting") + + else: + answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm, + answers='Y/N'), + color="yellow") + if answer.upper() != "Y": + raise YunohostError("aborting") + + raw_app_list = app_list(raw=True) From babaf541b6df58143762b40177f7766f2e981776 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Sep 2019 17:42:56 +0200 Subject: [PATCH 51/92] Decent quality is now at least level 5 --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c85a017e4..1246b889e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -823,7 +823,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu level = raw_app_list[app_name_to_test].get("level", None) confirm = "danger" if state in ["working", "validated"]: - if isinstance(level, int) and level >= 3: + if isinstance(level, int) and level >= 5: confirm = None elif isinstance(level, int) and level > 0: confirm = "warning" From 6aebec4a3450920956892b26152b20ee0cbc611a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 27 Sep 2019 20:37:13 +0200 Subject: [PATCH 52/92] Residual .migrate() -> .run() --- src/yunohost/dyndns.py | 2 +- src/yunohost/regenconf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index a35d39736..70817b3fe 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -212,7 +212,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, from yunohost.tools import _get_migration_by_name migration = _get_migration_by_name("migrate_to_tsig_sha256") try: - migration.migrate(dyn_host, domain, key) + migration.run(dyn_host, domain, key) except Exception as e: logger.error(m18n.n('migrations_migration_has_failed', exception=e, diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 48129634a..b7a42dd9d 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -70,7 +70,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run or not os.path.exists(REGEN_CONF_FILE)): from yunohost.tools import _get_migration_by_name migration = _get_migration_by_name("decouple_regenconf_from_services") - migration.migrate() + migration.run() result = {} From 5ff9ff01cf04dd8886f00bbd77bfb532ab87e16f Mon Sep 17 00:00:00 2001 From: nr 458 h Date: Sun, 22 Sep 2019 12:33:25 +0000 Subject: [PATCH 53/92] Translated using Weblate (German) Currently translated at 29.4% (165 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/locales/de.json b/locales/de.json index 10087e12f..62dd97aaf 100644 --- a/locales/de.json +++ b/locales/de.json @@ -4,7 +4,7 @@ "admin_password_change_failed": "Passwort kann nicht geändert werden", "admin_password_changed": "Das Administrator-Kennwort wurde geändert", "app_already_installed": "{app:s} ist schon installiert", - "app_argument_choice_invalid": "Verwende einen der folgenden Werte {choices:s}", + "app_argument_choice_invalid": "Wähle einen der folgenden Werte '{choices:s}' für das Argument '{name:s}'", "app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name: s}': {error: s}", "app_argument_required": "Argument '{name:s}' wird benötigt", "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", @@ -20,9 +20,9 @@ "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden, ist die URL korrekt?", "app_unknown": "Unbekannte App", "app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden", - "app_upgraded": "{app:s} wurde erfolgreich aktualisiert", - "appslist_fetched": "Appliste {appslist:s} wurde erfolgreich heruntergelanden", - "appslist_removed": "Appliste {appslist:s} wurde erfolgreich entfernt", + "app_upgraded": "{app:s} aktualisiert", + "appslist_fetched": "Appliste {appslist:s} wurde erfolgreich gelanden", + "appslist_removed": "Appliste {appslist:s} wurde entfernt", "appslist_retrieve_error": "Entfernte Appliste {appslist:s} kann nicht empfangen werden: {error:s}", "appslist_unknown": "Appliste {appslist:s} ist unbekannt.", "ask_current_admin_password": "Derzeitiges Administrator-Kennwort", @@ -34,20 +34,20 @@ "ask_new_admin_password": "Neues Verwaltungskennwort", "ask_password": "Passwort", "backup_action_required": "Du musst etwas zum Speichern auswählen", - "backup_app_failed": "Konnte keine Sicherung für '{app:s}' erstellen", + "backup_app_failed": "Konnte keine Sicherung für die App '{app:s}' erstellen", "backup_archive_app_not_found": "App '{app:s}' konnte in keiner Datensicherung gefunden werden", "backup_archive_hook_not_exec": "Hook '{hook:s}' konnte für diese Datensicherung nicht ausgeführt werden", - "backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits", + "backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits.", "backup_archive_name_unknown": "Unbekanntes lokale Datensicherung mit Namen '{name:s}' gefunden", "backup_archive_open_failed": "Kann Sicherungsarchiv nicht öfnen", "backup_cleaning_failed": "Temporäres Sicherungsverzeichnis konnte nicht geleert werden", "backup_created": "Datensicherung komplett", "backup_creating_archive": "Datensicherung wird erstellt…", "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", - "backup_deleted": "Datensicherung wurde entfernt", + "backup_deleted": "Backup wurde entfernt", "backup_extracting_archive": "Entpacke Sicherungsarchiv...", "backup_hook_unknown": "Datensicherungshook '{hook:s}' unbekannt", - "backup_invalid_archive": "Ungültige Datensicherung", + "backup_invalid_archive": "Dies ist kein Backup-Archiv", "backup_nothings_done": "Es gibt keine Änderungen zur Speicherung", "backup_output_directory_forbidden": "Verbotenes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", "backup_output_directory_not_empty": "Ausgabeordner ist nicht leer", @@ -213,7 +213,7 @@ "app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt", "service_regenconf_failed": "Konnte die Konfiguration für folgende Dienste nicht neu erzeugen: {services}", "not_enough_disk_space": "Zu wenig freier Speicherplatz unter '{path:s}' verfügbar", - "backup_creation_failed": "Erstellen des Backups fehlgeschlagen", + "backup_creation_failed": "Konnte Backup-Archiv nicht erstellen", "service_conf_up_to_date": "Die Konfiguration für den Dienst '{service}' ist bereits aktuell", "package_not_installed": "Das Paket '{pkgname}' ist nicht installiert", "pattern_positive_number": "Muss eine positive Zahl sein", @@ -277,28 +277,28 @@ "service_regenconf_pending_applying": "Überprüfe ausstehende Konfigurationen, die für den Server '{service}' notwendig sind...", "certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain {domain:s} mit der IP {ip:s}) zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.", "certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen - bitte versuche es später erneut.", - "appslist_retrieve_bad_format": "Die empfangene Datei der Appliste {appslist:s} ist ungültig", + "appslist_retrieve_bad_format": "Die geladene Appliste {appslist:s} ist ungültig", "domain_hostname_failed": "Erstellen des neuen Hostnamens fehlgeschlagen", "appslist_name_already_tracked": "Es gibt bereits eine registrierte App-Liste mit Namen {name:s}.", - "appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit dem URL {url:s}.", + "appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit der URL {url:s}.", "appslist_migrating": "Migriere Anwendungsliste {appslist:s} …", "appslist_could_not_migrate": "Konnte Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.", - "appslist_corrupted_json": "Konnte die Anwendungslisten. Es scheint, dass {filename:s} beschädigt ist.", + "appslist_corrupted_json": "Anwendungslisten konnte nicht geladen werden. Es scheint, dass {filename:s} beschädigt ist.", "yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.", "app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", "app_change_no_change_url_script": "Die Application {app_name:s} unterstützt das anpassen der URL noch nicht. Sie muss gegebenenfalls erweitert werden.", "app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain:s} {path:s}'). Es gibt nichts zu tun.", - "app_already_up_to_date": "{app:s} ist schon aktuell", + "app_already_up_to_date": "{app:s} ist bereits aktuell", "backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt", - "backup_applying_method_tar": "Erstellen des Backup-tar Archives...", + "backup_applying_method_tar": "Erstellen des Backup-tar Archives…", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup…", "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung.", "app_location_unavailable": "Diese URL ist entweder nicht verfügbar oder steht in Konflikt mit den bereits installierten Apps:\n{apps: s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen", - "backup_archive_writing_error": "Die Dateien konnten nicht in der komprimierte Archiv-Backup hinzugefügt werden", + "backup_archive_writing_error": "Die Dateien '{source:s} (im Ordner '{dest:s}') konnten nicht in das komprimierte Archiv-Backup '{archive:s}' hinzugefügt werden", "app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}", "backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…", "invalid_url_format": "ungültiges URL Format", @@ -332,15 +332,15 @@ "backup_custom_need_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Braucht ein Einhängen/Verbinden\" (need_mount) ein Fehler aufgetreten", "backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten", "backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten", - "backup_csv_creation_failed": "Die CSV-Datei, die für zukünftige Wiederherstellungsvorgänge erforderlich ist, kann nicht erstellt werden", + "backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden", "backup_couldnt_bind": "{Src: s} konnte nicht an {dest: s} angebunden werden.", "backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert", "backup_ask_for_copying_if_needed": "Einige Dateien konnten mit der Methode, die es vermeidet vorübergehend Speicherplatz auf dem System zu verschwenden, nicht gesichert werden. Zur Durchführung der Sicherung sollten vorübergehend {size: s} MB verwendet werden. Sind Sie einverstanden?", - "backup_actually_backuping": "Erstelle nun ein Backup-Archiv aus den gesammelten Dateien …", + "backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …", "ask_path": "Pfad", "ask_new_path": "Neuer Pfad", "ask_new_domain": "Neue Domain", - "apps_permission_restoration_failed": "Die Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} ist fehlgeschlagen", + "apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} erforderlich", "apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden", "app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden", "app_upgrade_app_name": "{App} wird jetzt aktualisiert…", @@ -354,5 +354,7 @@ "aborting": "Breche ab.", "app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}", "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", - "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen" + "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", + "app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}", + "apps_already_up_to_date": "Alle Apps sind bereits aktuell" } From b769111247f0b06fb99d17e86eb82eddc30c450b Mon Sep 17 00:00:00 2001 From: Aksel Kiesling Date: Sun, 22 Sep 2019 14:36:19 +0000 Subject: [PATCH 54/92] Translated using Weblate (German) Currently translated at 29.4% (165 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index 62dd97aaf..f9e9bbdbd 100644 --- a/locales/de.json +++ b/locales/de.json @@ -294,7 +294,7 @@ "backup_applying_method_tar": "Erstellen des Backup-tar Archives…", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup…", "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung.", - "app_location_unavailable": "Diese URL ist entweder nicht verfügbar oder steht in Konflikt mit den bereits installierten Apps:\n{apps: s}", + "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen", @@ -356,5 +356,6 @@ "already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.", "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", "app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}", - "apps_already_up_to_date": "Alle Apps sind bereits aktuell" + "apps_already_up_to_date": "Alle Apps sind bereits aktuell", + "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren" } From bea512c7a6bd453b578ef9e7a244dc0e03a23b4b Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 25 Sep 2019 06:55:50 +0000 Subject: [PATCH 55/92] Translated using Weblate (French) Currently translated at 71.9% (404 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 81 +++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index c94406a07..5350f3073 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -17,11 +17,11 @@ "app_manifest_invalid": "Manifeste d’application incorrect : {error}", "app_no_upgrade": "Aucune application à mettre à jour", "app_not_correctly_installed": "{app:s} semble être mal installé", - "app_not_installed": "L'application « {app:s} » n’est pas installée. Voici la liste des applications installées: {all_apps}", + "app_not_installed": "Nous n’avons pas trouvé l’application « {app:s} » dans la liste des applications installées: {all_apps}", "app_not_properly_removed": "{app:s} n’a pas été supprimé correctement", "app_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour être en adéquation avec les changements de YunoHost", "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", - "app_removed": "{app:s} a été supprimé", + "app_removed": "{app:s} supprimé", "app_requirements_checking": "Vérification des paquets requis pour {app} …", "app_requirements_failed": "Impossible de satisfaire les pré-requis pour {app} : {error}", "app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", @@ -29,8 +29,8 @@ "app_unknown": "Application inconnue", "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s}", - "app_upgraded": "{app:s} a été mis à jour", - "appslist_fetched": "La liste d’applications {appslist:s} a été récupérée", + "app_upgraded": "{app:s} mis à jour", + "appslist_fetched": "La liste d’applications {appslist:s} récupérée", "appslist_removed": "La liste d’applications {appslist:s} a été supprimée", "appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante {appslist:s} : {error:s}", "appslist_unknown": "La liste d’applications {appslist:s} est inconnue.", @@ -93,7 +93,7 @@ "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", "dyndns_ip_updated": "Votre adresse IP a été mise à jour pour le domaine DynDNS", - "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre un certain temps …", + "dyndns_key_generating": "Génération de la clé DNS ... , cela peut prendre un certain temps.", "dyndns_key_not_found": "Clé DNS introuvable pour le domaine", "dyndns_no_domain_registered": "Aucun domaine n’a été enregistré avec DynDNS", "dyndns_registered": "Le domaine DynDNS a été enregistré", @@ -182,7 +182,7 @@ "restore_running_hooks": "Exécution des scripts de restauration …", "service_add_configuration": "Ajout du fichier de configuration {file:s}", "service_add_failed": "Impossible d’ajouter le service '{service:s}'", - "service_added": "Le service '{service:s}' a été ajouté", + "service_added": "Le service '{service:s}' ajouté", "service_already_started": "Le service '{service:s}' est déjà démarré", "service_already_stopped": "Le service '{service:s}' est déjà arrêté", "service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'", @@ -280,14 +280,14 @@ "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur YunoHost. Cela peut se produire si vous avez récemment modifié votre enregistrement DNS. Si c'est le cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", "certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", - "appslist_retrieve_bad_format": "Le fichier récupéré pour la liste d’applications {appslist:s} n’est pas valide", - "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (mais ce n’est pas sûr… peut-être que ça n’en causera pas).", + "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites {appslist: s}", + "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (peut-être que ça n’en causera pas).", "yunohost_ca_creation_success": "L’autorité de certification locale a été créée.", - "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s}.", + "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s} existe déjà.", "appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.", "appslist_migrating": "Migration de la liste d’applications {appslist:s} …", "appslist_could_not_migrate": "Impossible de migrer la liste {appslist:s} ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", - "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit corrompu.", + "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit endommager.", "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez si cela est disponible avec `app changeurl`.", "app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.", "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", @@ -297,13 +297,13 @@ "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps:s}", "app_already_up_to_date": "{app:s} est déjà à jour", "invalid_url_format": "Format d’URL non valide", - "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {received_type:s}, mais les valeurs possibles sont : {expected_type:s}", + "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {choice:s}, mais les valeurs possibles sont : {available_choices:s}", "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu {received_type:s} alors que {expected_type:s} était attendu", "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason:s}", "global_settings_cant_serialize_setings": "Échec de sérialisation des données de configurations, cause : {reason:s}", "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason:s}", "global_settings_key_doesnt_exists": "La clef '{settings_key:s}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", - "global_settings_reset_success": "Super ! Vos configurations précédentes ont été sauvegardées dans {path:s}", + "global_settings_reset_success": "Vos configurations précédentes ont été sauvegardées dans {path:s}", "global_settings_setting_example_bool": "Exemple d’option booléenne", "global_settings_setting_example_int": "Exemple d’option de type entier", "global_settings_setting_example_string": "Exemple d’option de type chaîne", @@ -313,7 +313,7 @@ "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", "service_conf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par le service {service} mais a été conservé.", "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", - "backup_applying_method_tar": "Création de l’archive tar de la sauvegarde …", + "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde …", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup…", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", @@ -354,17 +354,17 @@ "migrations_current_target": "La cible de migration est {}", "migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}", "migrations_forward": "Migration en avant", - "migrations_loading_migration": "Chargement de la migration {number} {name} …", - "migrations_migration_has_failed": "La migration {number} {name} a échoué avec l’exception {exception} : annulation", + "migrations_loading_migration": "Chargement de la migration {id} …", + "migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", "migrations_show_currently_running_migration": "Application de la migration {number} {name} …", "migrations_show_last_migration": "La dernière migration appliquée est {}", - "migrations_skip_migration": "Ignorer et passer la migration {number} {name}…", + "migrations_skip_migration": "Ignorer et passer la migration {id}…", "server_shutdown": "Le serveur va éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]", - "app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications", + "app_upgrade_some_app_failed": "Certaines applications n’ont pas été mises à jour", "ask_path": "Chemin", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", @@ -373,12 +373,12 @@ "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.", "migrate_tsig_end": "La migration à hmac-sha512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", - "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers hmac-sha512 qui est plus sécurisé", - "migrate_tsig_wait": "Attendre 3 minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", + "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé", + "migrate_tsig_wait": "Attendre trois minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", "migrate_tsig_wait_2": "2 minutes …", "migrate_tsig_wait_3": "1 minute …", "migrate_tsig_wait_4": "30 secondes …", - "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire !", + "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire.", "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' est obsolète ! Utilisez 'app register-url' en remplacement !", "migration_description_0001_change_cert_group_to_sslcert": "Changement des permissions de groupe des certificats de « metronome » à « ssl-cert »", "migration_description_0002_migrate_to_tsig_sha256": "Amélioration de la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5", @@ -397,10 +397,10 @@ "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme « fonctionnelles ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", - "migrations_to_be_ran_manually": "La migration {number} {name} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", + "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", "migrations_need_to_accept_disclaimer": "Pour lancer la migration {number} {name}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", "service_description_avahi-daemon": "permet d’atteindre votre serveur via yunohost.local sur votre réseau local", - "service_description_dnsmasq": "gère la résolution des noms de domaine (DNS)", + "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", "service_description_dovecot": "permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", "service_description_fail2ban": "protège contre les attaques brute-force et autres types d’attaques venant d’Internet", "service_description_glances": "surveille les informations système de votre serveur", @@ -410,19 +410,19 @@ "service_description_nslcd": "gère la connexion en ligne de commande des utilisateurs YunoHost", "service_description_php5-fpm": "exécute des applications écrites en PHP avec nginx", "service_description_postfix": "utilisé pour envoyer et recevoir des courriels", - "service_description_redis-server": "une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", + "service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", "service_description_rmilter": "vérifie divers paramètres dans les courriels", "service_description_rspamd": "filtre le pourriel, et d’autres fonctionnalités liées au courriel", "service_description_slapd": "stocke les utilisateurs, domaines et leurs informations liées", "service_description_ssh": "vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", "service_description_yunohost-api": "permet les interactions entre l’interface web de YunoHost et le système", - "service_description_yunohost-firewall": "gère l'ouverture et la fermeture des ports de connexion aux services", + "service_description_yunohost-firewall": "Gère l'ouverture et la fermeture des ports de connexion aux services", "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", - "log_corrupted_md_file": "Le fichier yaml de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", + "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", - "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de fournir le journal historisé complet de l’opération en cliquant ici", + "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci cliqué ici pour avoir de l'aide", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, vos applications php pourraient ne pas être restaurées (reason: {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", @@ -468,7 +468,7 @@ "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", "migration_0005_not_enough_space": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.", "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", - "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec Nginx", + "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.", @@ -481,8 +481,8 @@ "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", - "aborting": "Opération annulée.", - "app_not_upgraded": "Les applications suivantes n'ont pas été mises à jour : {apps}", + "aborting": "Annulation.", + "app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}", "app_start_install": "Installation de l'application {app} …", "app_start_remove": "Suppression de l'application {app} …", "app_start_backup": "Collecte des fichiers devant être sauvegardés pour {app} …", @@ -507,9 +507,9 @@ "migration_0007_cancelled": "YunoHost n'a pas réussi à améliorer la façon dont est gérée votre configuration SSH.", "migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d'annuler la migration numéro 6.", "migration_0008_general_disclaimer": "Pour améliorer la sécurité de votre serveur, il est recommandé de laisser YunoHost gérer la configuration SSH. Votre configuration SSH actuelle diffère de la configuration recommandée. Si vous laissez YunoHost la reconfigurer, la façon dont vous vous connectez à votre serveur via SSH changera comme suit :", - "migration_0008_port": " - vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;", - "migration_0008_root": " - vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;", - "migration_0008_dsa": " - la clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;", + "migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;", + "migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;", + "migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;", "migration_0008_warning": "Si vous comprenez ces avertissements et que vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.", "migration_0008_no_warning": "Aucun risque majeur n'a été identifié concernant l'écrasement de votre configuration SSH - mais nous ne pouvons pas en être absolument sûrs ;) ! Si vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.", "migrations_success": "Migration {number} {name} réussie !", @@ -549,16 +549,16 @@ "regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'", "regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' …", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.", - "tools_upgrade_at_least_one": "Veuillez spécifier --apps OU --system", + "tools_upgrade_at_least_one": "Veuillez spécifier '--apps' OU '--system'", "tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps", - "tools_upgrade_cant_hold_critical_packages": "Impossibilité de maintenir les paquets critiques...", - "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost) ...", + "tools_upgrade_cant_hold_critical_packages": "Impossibilité de maintenir les paquets critiques…", + "tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost) …", "tools_upgrade_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}", - "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) ...", + "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", "updating_app_lists": "Récupération des mises à jour des applications disponibles…", "dpkg_lock_not_available": "Cette commande ne peut être lancée maintenant car il semblerai qu'un autre programme utilise déjà le verrou dpkg du gestionnaire de paquets du système", - "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques ...", + "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques …", "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", @@ -566,8 +566,8 @@ "apps_permission_restoration_failed": "L'autorisation '{permission:s}' pour la restauration de l'application {app:s} a échoué", "backup_permission": "Autorisation de sauvegarde pour l'application {app:s}", "edit_group_not_allowed": "Vous n'êtes pas autorisé à modifier le groupe {group:s}", - "error_when_removing_sftpuser_group": "Erreur en essayant de supprimer le groupe sftpusers", - "group_created": "Le groupe '{group}' a créé avec succès", + "error_when_removing_sftpuser_group": "Vous ne pouvez pas supprimer le groupe sftpusers", + "group_created": "Le groupe '{group}' a été créé", "group_deleted": "Le groupe '{group}' a été supprimé", "group_deletion_not_allowed": "Le groupe {group:s} ne peut pas être supprimé manuellement.", "group_info_failed": "L'information sur le groupe a échoué", @@ -588,5 +588,6 @@ "log_user_group_update": "Mettre à jour '{}' pour le groupe", "log_user_permission_add": "Mettre à jour l'autorisation pour '{}'", "log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'", - "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}" + "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", + "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}" } From 42b85356926f1908785615d3c43ce867d244b1c5 Mon Sep 17 00:00:00 2001 From: htsr Date: Wed, 25 Sep 2019 13:50:42 +0000 Subject: [PATCH 56/92] Translated using Weblate (French) Currently translated at 71.9% (404 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 5350f3073..87f806a21 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -524,7 +524,7 @@ "service_reloaded_or_restarted": "Le service '{service:s}' a été rechargé ou redémarré", "this_action_broke_dpkg": "Cette action a laissé des paquets non configurés par dpkg/apt (les gestionnaires de paquets système). Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a`.", "app_action_cannot_be_ran_because_required_services_down": "Cette application requiert certains services qui sont actuellement arrêtés. Avant de continuer, vous devriez essayer de redémarrer les services suivants (et éventuellement rechercher pourquoi ils sont arrêtés) : {services}", - "admin_password_too_long": "Choisissez un mot de passe plus court que 127 caractères", + "admin_password_too_long": "Veuillez choisir un mot de passe de moins de 127 caractères", "log_regen_conf": "Régénérer les configurations du système '{}'", "migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l'ignore.", "regenconf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé sous '{backup}'", From 50e9ac0087b94efcb3f9f0fce267c572d3be4b12 Mon Sep 17 00:00:00 2001 From: advocatux Date: Sun, 22 Sep 2019 16:43:19 +0000 Subject: [PATCH 57/92] Translated using Weblate (Spanish) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index d219b36e1..ff4a230f9 100644 --- a/locales/es.json +++ b/locales/es.json @@ -606,5 +606,7 @@ "aborting": "Cancelando.", "app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque la aplicación anterior no se pudo actualizar", "app_action_broke_system": "Esta acción parece que ha roto estos importantes servicios: {services}", - "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?" + "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", + "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", + "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído." } From ab3c159318a4208f58a8f8a3cd39a4835e4024fc Mon Sep 17 00:00:00 2001 From: htsr Date: Wed, 25 Sep 2019 13:52:15 +0000 Subject: [PATCH 58/92] Translated using Weblate (French) Currently translated at 76.2% (428 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 87f806a21..cdb952387 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -462,7 +462,7 @@ "log_tools_shutdown": "Éteindre votre serveur", "log_tools_reboot": "Redémarrer votre serveur", "mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur", - "migration_description_0004_php5_to_php7_pools": "Reconfiguration des groupes PHP pour utiliser PHP 7 au lieu de PHP 5", + "migration_description_0004_php5_to_php7_pools": "Reconfigurez les groupes PHP pour utiliser PHP 7 au lieu de PHP 5", "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", @@ -564,11 +564,11 @@ "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "apps_permission_not_found": "Aucune permission trouvée pour les applications installées", "apps_permission_restoration_failed": "L'autorisation '{permission:s}' pour la restauration de l'application {app:s} a échoué", - "backup_permission": "Autorisation de sauvegarde pour l'application {app:s}", + "backup_permission": "Permission de sauvegarde pour l'application {app:s}", "edit_group_not_allowed": "Vous n'êtes pas autorisé à modifier le groupe {group:s}", "error_when_removing_sftpuser_group": "Vous ne pouvez pas supprimer le groupe sftpusers", "group_created": "Le groupe '{group}' a été créé", - "group_deleted": "Le groupe '{group}' a été supprimé", + "group_deleted": "Suppression du groupe '{group}'", "group_deletion_not_allowed": "Le groupe {group:s} ne peut pas être supprimé manuellement.", "group_info_failed": "L'information sur le groupe a échoué", "group_unknown": "Le groupe {group:s} est inconnu", From d5999a122594bf0772c11ffd16d40405c6c8eff7 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 25 Sep 2019 13:53:22 +0000 Subject: [PATCH 59/92] Translated using Weblate (French) Currently translated at 76.2% (428 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index cdb952387..03c274f5a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -557,7 +557,7 @@ "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", "updating_app_lists": "Récupération des mises à jour des applications disponibles…", - "dpkg_lock_not_available": "Cette commande ne peut être lancée maintenant car il semblerai qu'un autre programme utilise déjà le verrou dpkg du gestionnaire de paquets du système", + "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets).", "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques …", "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", From d26fa71d97de9d4c92edc3aacaf3f826871d45d5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Wed, 25 Sep 2019 19:14:11 +0000 Subject: [PATCH 60/92] Translated using Weblate (French) Currently translated at 76.2% (428 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 03c274f5a..54ea1db46 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -46,13 +46,13 @@ "backup_app_failed": "Impossible de sauvegarder l’application '{app:s}'", "backup_archive_app_not_found": "L’application '{app:s}' n’a pas été trouvée dans l’archive de la sauvegarde", "backup_archive_hook_not_exec": "Le script « {hook:s} » n'a pas été exécuté dans cette sauvegarde", - "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà", + "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà.", "backup_archive_name_unknown": "L’archive locale de sauvegarde nommée '{name:s}' est inconnue", - "backup_archive_open_failed": "Impossible d’ouvrir l’archive de sauvegarde", + "backup_archive_open_failed": "Impossible d’ouvrir l’archive de la sauvegarde", "backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde", "backup_created": "Sauvegarde terminée", "backup_creating_archive": "Création de l’archive de sauvegarde …", - "backup_creation_failed": "Impossible de créer la sauvegarde", + "backup_creation_failed": "Impossible de créer l'archive de la sauvegarde", "backup_delete_error": "Impossible de supprimer '{path:s}'", "backup_deleted": "La sauvegarde a été supprimée", "backup_extracting_archive": "Extraction de l’archive de sauvegarde …", @@ -259,8 +259,8 @@ "certmanager_error_no_A_record": "Aucun enregistrement DNS 'A' n’a été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", - "certmanager_cert_install_success_selfsigned": "Installation avec succès d’un certificat auto-signé pour le domaine {domain:s} !", - "certmanager_cert_install_success": "Installation avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", + "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", + "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué", @@ -312,8 +312,8 @@ "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", "service_conf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par le service {service} mais a été conservé.", - "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", - "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde …", + "backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter", + "backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde…", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup…", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", @@ -324,20 +324,20 @@ "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", - "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire aux opérations futures de restauration", + "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire à la restauration", "backup_csv_addition_failed": "Impossible d’ajouter des fichiers à sauvegarder dans le fichier CSV", "backup_custom_need_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'need_mount'", "backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'backup'", "backup_custom_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'mount'", - "backup_no_uncompress_archive_dir": "Le dossier de l’archive décompressée n’existe pas", - "backup_method_tar_finished": "L’archive tar de la sauvegarde a été créée", + "backup_no_uncompress_archive_dir": "Ce dossier d’archive décompressée n’existe pas", + "backup_method_tar_finished": "L’archive TAR de la sauvegarde a été créée", "backup_method_copy_finished": "La copie de la sauvegarde est terminée", "backup_method_borg_finished": "La sauvegarde dans Borg est terminée", "backup_method_custom_finished": "La méthode de sauvegarde personnalisée '{method:s}' est terminée", "backup_system_part_failed": "Impossible de sauvegarder la partie '{part:s}' du système", - "backup_unable_to_organize_files": "Impossible d’organiser les fichiers dans l’archive avec la méthode rapide", + "backup_unable_to_organize_files": "Impossible d’utiliser la méthode rapide pour organiser les fichiers dans l’archive", "backup_with_no_backup_script_for_app": "L’application {app:s} n’a pas de script de sauvegarde. Ignorer.", - "backup_with_no_restore_script_for_app": "L’application {app:s} n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", + "backup_with_no_restore_script_for_app": "L’application « {app:s} » n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive …", @@ -423,7 +423,7 @@ "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci cliqué ici pour avoir de l'aide", - "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, vos applications php pourraient ne pas être restaurées (reason: {error:s})", + "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", @@ -589,5 +589,7 @@ "log_user_permission_add": "Mettre à jour l'autorisation pour '{}'", "log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'", "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", - "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}" + "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}", + "apps_already_up_to_date": "Toutes les applications sont déjà à jour", + "app_upgrade_stopped": "La mise à jour de toutes les applications a été arrêtée afin d’éviter d’éventuels dommages dus à l’échec de la mise à jour de l’application précédente" } From 81bb7bffd90994ae36f9c9795a0163bef5fe8b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=B6ring?= Date: Mon, 23 Sep 2019 17:51:54 +0000 Subject: [PATCH 61/92] Translated using Weblate (German) Currently translated at 40.7% (229 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 58 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index f9e9bbdbd..2e3777207 100644 --- a/locales/de.json +++ b/locales/de.json @@ -357,5 +357,61 @@ "admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen", "app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}", "apps_already_up_to_date": "Alle Apps sind bereits aktuell", - "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren" + "backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren", + "app_upgrade_stopped": "Das Upgrade aller Anwendungen wurde gestoppt, um mögliche Schäden zu vermeiden, da das Upgrade der vorherigen Anwendung fehlgeschlagen ist", + "group_already_disallowed": "Die Gruppe '{group:s}' hat bereits die Berechtigungen '{permission:s}' für die App '{app:s}' deaktiviert", + "global_settings_setting_security_ssh_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den SSH-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "group_deleted": "Gruppe '{group}' gelöscht", + "group_deletion_failed": "Kann Gruppe '{group}' nicht löschen", + "group_deletion_not_allowed": "Die Gruppe {group:s} kann nicht manuell gelöscht werden.", + "dyndns_provider_unreachable": "Dyndns-Anbieter {provider} kann nicht erreicht werden: Entweder ist dein YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.", + "group_already_allowed": "Gruppe '{group:s}' hat bereits die Berechtigung '{permission:s}' für die App '{app:s}' eingeschaltet", + "group_name_already_exist": "Gruppe {name:s} existiert bereits", + "group_created": "Gruppe '{group}' angelegt", + "group_creation_failed": "Kann Gruppe '{group}' nicht anlegen", + "group_unknown": "Die Gruppe '{group:s}' ist unbekannt", + "group_updated": "Gruppe '{group:s}' erneuert", + "group_update_failed": "Kann Gruppe '{group:s}' nicht anpassen", + "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", + "log_app_removelist": "Entferne eine Applikationsliste", + "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", + "log_app_removeaccess": "Entziehe Zugriff auf '{}'", + "global_settings_setting_security_postfix_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Postfix-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "log_category_404": "Die Log-Kategorie '{category}' existiert nicht", + "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting:s} scheint den Typ {unknown_type:s} zu haben, ist aber kein vom System unterstützter Typ.", + "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint.... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo dpkg --configure -a` ausführst.", + "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key:s}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", + "log_link_to_log": "Vollständiges Log dieser Operation: '{desc}'", + "global_settings_setting_example_bool": "Beispiel einer booleschen Option", + "log_app_fetchlist": "Füge eine Applikationsliste hinzu", + "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwende den Befehl 'yunohost log display {name}'", + "global_settings_setting_security_nginx_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Webserver NGINX. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)", + "backup_php5_to_php7_migration_may_fail": "Dein Archiv konnte nicht für PHP 7 konvertiert werden, Du kannst deine PHP-Anwendungen möglicherweise nicht wiederherstellen (Grund: {error:s})", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration", + "global_settings_setting_example_string": "Beispiel einer string Option", + "log_app_addaccess": "Füge Zugriff auf '{}' hinzu", + "log_app_remove": "Entferne die Anwendung '{}'", + "global_settings_setting_example_int": "Beispiel einer int Option", + "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason:s}", + "global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason:s}", + "log_app_install": "Installiere die Anwendung '{}'", + "global_settings_reset_success": "Frühere Einstellungen werden nun auf {path:s} gesichert", + "log_app_upgrade": "Upgrade der Anwendung '{}'", + "good_practices_about_admin_password": "Sie sind nun dabei, ein neues Administrationspasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - obwohl es sinnvoll ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", + "log_corrupted_md_file": "Die mit Protokollen verknüpfte YAML-Metadatendatei ist beschädigt: '{md_file}\nFehler: {error}''", + "global_settings_cant_serialize_settings": "Einstellungsdaten konnten nicht serialisiert werden, Grund: {reason:s}", + "log_help_to_get_failed_log": "Der Vorgang'{desc}' konnte nicht abgeschlossen werden. Bitte teile das vollständige Protokoll dieser Operation mit dem Befehl 'yunohost log display {name} --share', um Hilfe zu erhalten", + "backup_no_uncompress_archive_dir": "Dieses unkomprimierte Archivverzeichnis gibt es nicht", + "log_app_change_url": "Ändere die URL der Anwendung '{}'", + "global_settings_setting_security_password_user_strength": "Stärke des Benutzerpassworts", + "good_practices_about_user_password": "Du bist nun dabei, ein neues Benutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - obwohl es ratsam ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", + "global_settings_setting_example_enum": "Beispiel einer enum Option", + "log_link_to_failed_log": "Der Vorgang konnte nicht abgeschlossen werden '{desc}'. Bitte gib das vollständige Protokoll dieser Operation mit Klicken Sie hier an, um Hilfe zu erhalten", + "backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden", + "backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden", + "log_app_clearaccess": "Entziehe alle Zugriffe auf '{}'", + "global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts", + "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", + "log_app_makedefault": "Mache '{}' zur Standard-Anwendung", + "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}" } From cb3a2a3adb2d5ccf6194a7aae06dbdf009d11043 Mon Sep 17 00:00:00 2001 From: nr 458 h Date: Tue, 24 Sep 2019 08:33:31 +0000 Subject: [PATCH 62/92] Translated using Weblate (German) Currently translated at 40.7% (229 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/de.json b/locales/de.json index 2e3777207..d03226187 100644 --- a/locales/de.json +++ b/locales/de.json @@ -46,11 +46,11 @@ "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", "backup_deleted": "Backup wurde entfernt", "backup_extracting_archive": "Entpacke Sicherungsarchiv...", - "backup_hook_unknown": "Datensicherungshook '{hook:s}' unbekannt", + "backup_hook_unknown": "Der Datensicherungshook '{hook:s}' unbekannt", "backup_invalid_archive": "Dies ist kein Backup-Archiv", - "backup_nothings_done": "Es gibt keine Änderungen zur Speicherung", - "backup_output_directory_forbidden": "Verbotenes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", - "backup_output_directory_not_empty": "Ausgabeordner ist nicht leer", + "backup_nothings_done": "Keine Änderungen zur Speicherung", + "backup_output_directory_forbidden": "Wähle ein anderes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden", + "backup_output_directory_not_empty": "Der gewählte Ausgabeordner sollte leer sein", "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", "backup_running_app_script": "Datensicherung für App '{app:s}' wurd durchgeführt...", "backup_running_hooks": "Datensicherunghook wird ausgeführt…", @@ -325,7 +325,7 @@ "backup_permission": "Sicherungsberechtigung für App {app: s}", "backup_output_symlink_dir_broken": "Sie haben einen fehlerhaften Symlink anstelle Ihres Archivverzeichnisses '{path: s}'. Möglicherweise haben Sie ein spezielles Setup, um Ihre Daten auf einem anderen Dateisystem zu sichern. In diesem Fall haben Sie wahrscheinlich vergessen, Ihre Festplatte oder Ihren USB-Schlüssel erneut einzuhängen oder anzuschließen.", "backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten…", - "backup_method_tar_finished": "Sicherungs-Tar-Archiv erstellt", + "backup_method_tar_finished": "Tar-Backup-Archiv erstellt", "backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method: s}' beendet", "backup_method_copy_finished": "Sicherungskopie beendet", "backup_method_borg_finished": "Backup in Borg beendet", From e1ca92234aeac72fe2758b76acfdf29361158310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 24 Sep 2019 00:14:38 +0000 Subject: [PATCH 63/92] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 24.9% (140 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nb_NO/ --- locales/nb_NO.json | 170 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 0967ef424..4fe62eebb 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -1 +1,169 @@ -{} +{ + "aborting": "Avbryter…", + "admin_password": "Administrasjonspassord", + "admin_password_change_failed": "Kan ikke endre passord", + "admin_password_changed": "Administrasjonspassord endret", + "admin_password_too_long": "Velg et passord kortere enn 127 tegn", + "app_already_installed": "{app:s} er allerede installert", + "app_already_up_to_date": "{app:s} er allerede oppdatert", + "app_argument_invalid": "Velg en gydlig verdi for argumentet '{name:s}': {error:s}", + "app_argument_required": "Argumentet '{name:s}' er påkrevd", + "diagnosis_no_apps": "Inget program installert", + "app_id_invalid": "Ugyldig program-ID", + "dyndns_cron_remove_failed": "Kunne ikke fjerne cron-jobb for DynDNS: {error}", + "dyndns_key_not_found": "Fant ikke DNS-nøkkel for domenet", + "app_not_correctly_installed": "{app:s} ser ikke ut til å ha blitt installert på riktig måte", + "dyndns_provider_unreachable": "Kunne ikke nå DynDNS-tilbyder {provider}: Enten har du ikke satt opp din YunoHost rett, dynette-tjeneren er nede, eller du mangler nett.", + "app_not_properly_removed": "{app:s} har ikke blitt fjernet på riktig måte", + "app_package_need_update": "Programmet {app}-pakken må oppdateres for å holde følge med YunoHost sine endringer", + "app_removed": "{app:s} fjernet", + "app_requirements_checking": "Sjekker påkrevde pakker for {app:s}…", + "app_requirements_failed": "Noen krav er ikke oppfylt for {app:s}: {error}", + "app_start_install": "Installerer programmet '{app}'…", + "action_invalid": "Ugyldig handling '{action:s}'", + "app_start_restore": "Gjenoppretter programmet '{app}'…", + "backup_created": "Sikkerhetskopi opprettet", + "backup_archive_name_exists": "En sikkerhetskopi med dette navnet finnes allerede.", + "backup_archive_name_unknown": "Ukjent lokalt sikkerhetskopiarkiv ved navn '{name:s}'", + "already_up_to_date": "Ingenting å gjøre. Alt er oppdatert.", + "backup_method_copy_finished": "Sikkerhetskopi fullført", + "backup_method_tar_finished": "TAR-sikkerhetskopiarkiv opprettet", + "app_action_cannot_be_ran_because_required_services_down": "Dette programmet krever noen tjenester som ikke kjører. Før du fortsetter, du bør prøve å starte følgende tjenester på ny (og antagelig undersøke hvorfor de er nede): {services}", + "app_already_installed_cant_change_url": "Dette programmet er allerede installert. Nettadressen kan ikke endres kun med denne funksjonen. Ta en titt på `app changeurl` hvis den er tilgjengelig.", + "diagnosis_monitor_disk_error": "Kunne ikke holde oppsyn med disker: {error}", + "diagnosis_monitor_system_error": "Kunne ikke holde oppsyn med systemet: {error}", + "domain_exists": "Domenet finnes allerede", + "app_change_url_failed_nginx_reload": "Kunne ikke gjeninnlaste NGINX. Her har du utdataen for 'nginx -t'\n{nginx_errors:s}", + "domains_available": "Tilgjengelige domener:", + "done": "Ferdig", + "downloading": "Laster ned…", + "dyndns_could_not_check_provide": "Kunne ikke sjekke om {provider:s} kan tilby {domain:s}.", + "dyndns_could_not_check_available": "Kunne ikke sjekke om {domain:s} er tilgjengelig på {provider:s}.", + "log_app_removeaccess": "Fjern tilgang til '{}'", + "license_undefined": "udefinert", + "mail_domain_unknown": "Ukjent e-postadresse for domenet '{domain:s}'", + "migrate_tsig_wait_2": "2 min…", + "log_remove_on_failed_restore": "Fjern '{}' etter mislykket gjenoppretting fra sikkerhetskopiarkiv", + "log_letsencrypt_cert_install": "Installer et Let's Encrypt-sertifikat på '{}'-domenet", + "log_permission_update": "Oppdater tilgang '{}' for programmet '{}'", + "log_letsencrypt_cert_renew": "Forny '{}'-Let's Encrypt-sertifikat", + "log_user_update": "Oppdater brukerinfo for '{}'", + "mail_alias_remove_failed": "Kunne ikke fjerne e-postaliaset '{mail:s}'", + "app_action_broke_system": "Denne handlingen ser ut til å ha knekt disse viktige tjenestene: {services}", + "app_argument_choice_invalid": "Bruk én av disse valgene '{choices:s}' for argumentet '{name:s}'", + "app_extraction_failed": "Kunne ikke pakke ut installasjonsfilene", + "app_incompatible": "Programmet {app} er ikke kompatibelt med din YunoHost-versjon", + "app_install_files_invalid": "Disse filene kan ikke installeres", + "app_location_already_used": "Programmet '{app}' er allerede installert i ({path})", + "ask_path": "Sti", + "backup_abstract_method": "Denne sikkerhetskopimetoden er ikke implementert enda", + "backup_actually_backuping": "Oppretter sikkerhetskopiarkiv fra innsamlede filer…", + "backup_app_failed": "Kunne ikke sikkerhetskopiere programmet '{app:s}'", + "backup_applying_method_tar": "Lager TAR-sikkerhetskopiarkiv…", + "backup_archive_app_not_found": "Fant ikke programmet '{app:s}' i sikkerhetskopiarkivet", + "backup_archive_open_failed": "Kunne ikke åpne sikkerhetskopiarkivet", + "app_start_remove": "Fjerner programmet '{app}'…", + "app_start_backup": "Samler inn filer for sikkerhetskopiering for {app}…", + "backup_applying_method_copy": "Kopier alle filer til sikkerhetskopi…", + "backup_borg_not_implemented": "Borg-sikkerhetskopimetoden er ikke implementert enda", + "backup_creation_failed": "Kunne ikke opprette sikkerhetskopiarkiv", + "backup_couldnt_bind": "Kunne ikke binde {src:s} til {dest:s}.", + "backup_csv_addition_failed": "Kunne ikke legge til filer for sikkerhetskopi inn i CSV-filen", + "backup_deleted": "Sikkerhetskopi slettet", + "backup_no_uncompress_archive_dir": "Det finnes ingen slik utpakket arkivmappe", + "backup_delete_error": "Kunne ikke slette '{path:s}'", + "certmanager_domain_unknown": "Ukjent domene '{domain:s}'", + "certmanager_cert_signing_failed": "Kunne ikke signere det nye sertifikatet", + "diagnosis_debian_version_error": "Kunne ikke hente Debian-versjon: {error}", + "diagnosis_kernel_version_error": "Kunne ikke hente kjerneversjon: {error}", + "error_when_removing_sftpuser_group": "Kunne ikke fjerne sftpusers-gruppen", + "executing_command": "Kjører kommendoen '{command:s}'…", + "executing_script": "Kjører skriptet '{script:s}'…", + "extracting": "Pakker ut…", + "edit_group_not_allowed": "Du tillates ikke å redigere gruppen {group:s}", + "log_domain_add": "Legg til '{}'-domenet i systemoppsett", + "log_domain_remove": "Fjern '{}'-domenet fra systemoppsett", + "log_dyndns_subscribe": "Abonner på YunoHost-underdomenet '{}'", + "log_dyndns_update": "Oppdater IP-adressen tilknyttet ditt YunoHost-underdomene '{}'", + "migrate_tsig_wait_3": "1 min…", + "migrate_tsig_wait_4": "30 sekunder…", + "apps_permission_restoration_failed": "Innvilg tilgangen '{permission:s}' for å gjenopprette {app:}", + "apps_permission_not_found": "Fant ingen tilgang for de installerte programmene", + "backup_invalid_archive": "Dette er ikke et sikkerhetskopiarkiv", + "backup_nothings_done": "Ingenting å lagre", + "backup_method_borg_finished": "Sikkerhetskopi inn i Borg fullført", + "field_invalid": "Ugyldig felt '{:s}'", + "firewall_reloaded": "Brannmur gjeninnlastet", + "log_app_removelist": "Fjern en programliste", + "log_app_change_url": "Endre nettadresse for '{}'-programmet", + "log_app_install": "Installer '{}'-programmet", + "log_app_remove": "Fjern '{}'-programmet", + "log_app_upgrade": "Oppgrader '{}'-programmet", + "log_app_makedefault": "Gjør '{}' til forvalgt program", + "log_available_on_yunopaste": "Denne loggen er nå tilgjengelig via {url}", + "log_tools_maindomain": "Gjør '{}' til hoveddomene", + "log_tools_shutdown": "Slå av tjeneren din", + "log_tools_reboot": "Utfør omstart av tjeneren din", + "apps_already_up_to_date": "Alle programmer allerede oppdatert", + "backup_mount_archive_for_restore": "Forbereder arkiv for gjenopprettelse…", + "backup_copying_to_organize_the_archive": "Kopierer {size:s} MB for å organisere arkivet", + "domain_cannot_remove_main": "Kan ikke fjerne hoveddomene. Sett et først", + "domain_cert_gen_failed": "Kunne ikke opprette sertifikat", + "domain_created": "Domene opprettet", + "domain_creation_failed": "Kunne ikke opprette domene", + "domain_dyndns_root_unknown": "Ukjent DynDNS-rotdomene", + "domain_unknown": "Ukjent domene", + "dyndns_cron_installed": "Opprettet cron-jobb for DynDNS", + "dyndns_cron_removed": "Fjernet cron-jobb for DynDNS", + "dyndns_ip_update_failed": "Kunne ikke oppdatere IP-adresse til DynDNS", + "dyndns_ip_updated": "Oppdaterte din IP på DynDNS", + "dyndns_key_generating": "Oppretter DNS-nøkkel… Dette kan ta en stund.", + "dyndns_no_domain_registered": "Inget domene registrert med DynDNS", + "dyndns_registered": "DynDNS-domene registrert", + "global_settings_setting_security_password_admin_strength": "Admin-passordets styrke", + "dyndns_registration_failed": "Kunne ikke registrere DynDNS-domene: {error:s}", + "global_settings_setting_security_password_user_strength": "Brukerpassordets styrke", + "log_app_fetchlist": "Legg til en programliste", + "log_backup_restore_app": "Gjenopprett '{}' fra sikkerhetskopiarkiv", + "log_remove_on_failed_install": "Fjern '{}' etter mislykket installasjon", + "log_permission_add": "Legg til '{}'-tilgangen for programmet '{}'", + "log_permission_remove": "Fjern tilgangen '{}'", + "log_selfsigned_cert_install": "Installer selvsignert sertifikat på '{}'-domenet", + "log_user_delete": "Slett '{}' bruker", + "log_user_group_add": "Legg til '{}' gruppe", + "log_user_group_delete": "Slett '{}' gruppe", + "log_user_group_update": "Oppdater '{}' gruppe", + "log_user_permission_add": "Oppdater '{}' tilgang", + "log_user_permission_remove": "Oppdater '{}' tilgang", + "ldap_init_failed_to_create_admin": "LDAP-igangsettelse kunne ikke opprette admin-bruker", + "ldap_initialized": "LDAP-igangsatt", + "maindomain_changed": "Hoveddomenet er nå endret", + "migration_description_0003_migrate_to_stretch": "Oppgrader systemet til Debian Stretch og YunoHost 3.0", + "app_unknown": "Ukjent program", + "app_upgrade_app_name": "Oppgraderer {app}…", + "app_upgrade_failed": "Kunne ikke oppgradere {app:s}", + "app_upgrade_some_app_failed": "Noen programmer kunne ikke oppgraderes", + "app_upgraded": "{app:s} oppgradert", + "ask_email": "E-postadresse", + "ask_firstname": "Fornavn", + "ask_lastname": "Etternavn", + "ask_list_to_remove": "Liste å fjerne", + "ask_main_domain": "Hoveddomene", + "ask_new_admin_password": "Nytt administrasjonspassord", + "app_upgrade_several_apps": "Følgende programmer vil oppgraderes: {apps}", + "appslist_removed": "{appslist:s}-programliste fjernet", + "appslist_url_already_tracked": "Dette er allerede en registrert programliste med nettadressen {url:s}.", + "ask_current_admin_password": "Nåværende administrasjonspassord", + "appslist_unknown": "Programlisten {appslist:s} er ukjent.", + "ask_new_domain": "Nytt domene", + "ask_new_path": "Ny sti", + "ask_password": "Passord", + "domain_deleted": "Domene slettet", + "domain_deletion_failed": "Kunne ikke slette domene", + "domain_dyndns_already_subscribed": "Du har allerede abonnement på et DynDNS-domene", + "log_category_404": "Loggkategorien '{category}' finnes ikke", + "log_link_to_log": "Full logg for denne operasjonen: '{desc}'", + "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log display {name}'", + "log_app_clearaccess": "Fjern all tilgang til '{}'", + "log_user_create": "Legg til '{}' bruker" +} From a98d7f4750da8e62fc1839ea86c5247ee023c440 Mon Sep 17 00:00:00 2001 From: advocatux Date: Thu, 3 Oct 2019 19:28:23 +0000 Subject: [PATCH 64/92] Translated using Weblate (Spanish) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index ff4a230f9..02f46652b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -320,7 +320,7 @@ "experimental_feature": "Aviso : esta funcionalidad es experimental y no se considera estable, no debería usarla a menos que sepa lo que está haciendo.", "good_practices_about_user_password": "Está a punto de establecer una nueva contraseña de usuario. La contraseña debería de ser de al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).", "password_listed": "Esta contraseña es una de las más usadas en el mundo. Elija algo más único.", - "password_too_simple_1": "La contraseña tiene que ser de al menos 8 caracteres de longitud", + "password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud", "password_too_simple_2": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número y caracteres en mayúsculas y minúsculas", "password_too_simple_3": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", "password_too_simple_4": "La contraseña tiene que ser de al menos 12 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", From bd8a91fff7cdc29ed58fb5a649b8e93b6081e7f5 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 2 Oct 2019 05:49:03 +0000 Subject: [PATCH 65/92] Translated using Weblate (French) Currently translated at 100.0% (562 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 136 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 46 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 54ea1db46..8bffec8b2 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -88,15 +88,15 @@ "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", "done": "Terminé", "downloading": "Téléchargement en cours …", - "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été installée", + "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été créée", "dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron DynDNS parce que: {error}", - "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée", + "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS enlevée", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", - "dyndns_ip_updated": "Votre adresse IP a été mise à jour pour le domaine DynDNS", + "dyndns_ip_updated": "Mise à jour de votre IP pour le domaine DynDNS", "dyndns_key_generating": "Génération de la clé DNS ... , cela peut prendre un certain temps.", "dyndns_key_not_found": "Clé DNS introuvable pour le domaine", - "dyndns_no_domain_registered": "Aucun domaine n’a été enregistré avec DynDNS", - "dyndns_registered": "Le domaine DynDNS a été enregistré", + "dyndns_no_domain_registered": "Aucun domaine enregistré avec DynDNS", + "dyndns_registered": "Domaine DynDNS enregistré", "dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error:s}", "dyndns_unavailable": "Le domaine {domain:s} est indisponible.", "executing_command": "Exécution de la commande '{command:s}' …", @@ -104,8 +104,8 @@ "extracting": "Extraction en cours …", "field_invalid": "Champ incorrect : '{:s}'", "firewall_reload_failed": "Impossible de recharger le pare-feu", - "firewall_reloaded": "Le pare-feu a été rechargé", - "firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Pour plus d’informations, consultez le journal.", + "firewall_reloaded": "Pare-feu rechargé", + "firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Plus d'info dans le journal de log.", "format_datetime_short": "%d/%m/%Y %H:%M", "hook_argument_missing": "Argument manquant : '{:s}'", "hook_choice_invalid": "Choix incorrect : '{:s}'", @@ -114,16 +114,16 @@ "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", "hook_name_unknown": "Nom de l'action '{name:s}' inconnu", "installation_complete": "Installation terminée", - "installation_failed": "Échec de l’installation", + "installation_failed": "Quelque chose s'est mal passé lors de l'installation", "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", - "ldap_initialized": "L’annuaire LDAP a été initialisé", + "ldap_initialized": "L’annuaire LDAP initialisé", "license_undefined": "indéfinie", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", "mail_domain_unknown": "Le domaine '{domain:s}' pour l'adresse de courriel est inconnu", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "maindomain_change_failed": "Impossible de modifier le domaine principal", - "maindomain_changed": "Le domaine principal a été modifié", + "maindomain_changed": "Le domaine principal modifié", "monitor_disabled": "La supervision du serveur a été désactivé", "monitor_enabled": "La supervision du serveur a été activé", "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", @@ -173,7 +173,7 @@ "restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'", "restore_app_failed": "Impossible de restaurer l’application '{app:s}'", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", - "restore_complete": "Restauration terminée", + "restore_complete": "Restauré", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", "restore_failed": "Impossible de restaurer le système", "restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l'est pas non plus dans l’archive", @@ -218,9 +218,9 @@ "service_unknown": "Le service '{service:s}' est inconnu", "services_configured": "La configuration a été générée avec succès", "show_diff": "Voici les différences :\n{diff:s}", - "ssowat_conf_generated": "La configuration de SSOwat a été générée", - "ssowat_conf_updated": "La configuration de SSOwat a été mise à jour", - "system_upgraded": "Le système a été mis à jour", + "ssowat_conf_generated": "La configuration de SSOwat générée", + "ssowat_conf_updated": "La configuration de SSOwat mise à jour", + "system_upgraded": "Système mis à jour", "system_username_exists": "Ce nom d’utilisateur existe déjà dans les utilisateurs système", "unbackup_app": "L’application '{app:s}' ne sera pas sauvegardée", "unexpected_error": "Une erreur inattendue est survenue : {error}", @@ -232,12 +232,12 @@ "upgrade_complete": "Mise à jour terminée", "upgrading_packages": "Mise à jour des paquets en cours …", "upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé", - "upnp_disabled": "UPnP a été désactivé", - "upnp_enabled": "UPnP a été activé", + "upnp_disabled": "UPnP désactivé", + "upnp_enabled": "UPnP activé", "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", - "user_created": "L’utilisateur a été créé", + "user_created": "L’utilisateur créé", "user_creation_failed": "Impossible de créer l’utilisateur", - "user_deleted": "L’utilisateur a été supprimé", + "user_deleted": "L’utilisateur supprimé", "user_deletion_failed": "Impossible de supprimer l’utilisateur", "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", "user_info_failed": "Impossible de récupérer les informations de l’utilisateur", @@ -246,7 +246,7 @@ "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", - "yunohost_configured": "YunoHost a été configuré", + "yunohost_configured": "YunoHost maintenant configuré", "yunohost_installing": "L'installation de YunoHost est en cours …", "yunohost_not_installed": "YunoHost n’est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", @@ -261,15 +261,15 @@ "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", "certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »", "certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »", - "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", + "certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain: s}'", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", - "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué", + "certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat", "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", - "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration Nginx {filepath:s} est en conflit et doit être préalablement retiré", + "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", - "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", + "ssowat_persistent_conf_read_error": "Impossible de lire la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", + "ssowat_persistent_conf_write_error": "Impossible de sauvegarder de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Définissez d'abord un nouveau domaine principal", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", @@ -282,7 +282,7 @@ "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", "appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites {appslist: s}", "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (peut-être que ça n’en causera pas).", - "yunohost_ca_creation_success": "L’autorité de certification locale a été créée.", + "yunohost_ca_creation_success": "L’autorité de certification locale créée.", "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s} existe déjà.", "appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.", "appslist_migrating": "Migration de la liste d’applications {appslist:s} …", @@ -350,7 +350,7 @@ "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne. Erreur : {error}", "migrations_backward": "Migration en arrière.", "migrations_bad_value_for_target": "Nombre invalide pour le paramètre target, les numéros de migration sont 0 ou {}", - "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migrations avec le chemin %s", + "migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration sur le chemin% s", "migrations_current_target": "La cible de migration est {}", "migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}", "migrations_forward": "Migration en avant", @@ -371,7 +371,7 @@ "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de l’application {app} …", "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.", - "migrate_tsig_end": "La migration à hmac-sha512 est terminée", + "migrate_tsig_end": "La migration à HMAC-SHA-512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé", "migrate_tsig_wait": "Attendre trois minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", @@ -393,29 +393,29 @@ "migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !", "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …", - "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence !", + "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.", "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme « fonctionnelles ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", - "migrations_need_to_accept_disclaimer": "Pour lancer la migration {number} {name}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", - "service_description_avahi-daemon": "permet d’atteindre votre serveur via yunohost.local sur votre réseau local", + "migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", + "service_description_avahi-daemon": "Vous permet d’atteindre votre serveur en utilisant «yunohost.local» sur votre réseau local", "service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)", - "service_description_dovecot": "permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", - "service_description_fail2ban": "protège contre les attaques brute-force et autres types d’attaques venant d’Internet", - "service_description_glances": "surveille les informations système de votre serveur", - "service_description_metronome": "gère les comptes de messagerie instantanée XMPP", - "service_description_mysql": "stocke les données des applications (bases de données SQL)", - "service_description_nginx": "sert ou permet l’accès à tous les sites web hébergés sur votre serveur", - "service_description_nslcd": "gère la connexion en ligne de commande des utilisateurs YunoHost", + "service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", + "service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet", + "service_description_glances": "Surveille les info système de votre serveur", + "service_description_metronome": "Gère les comptes de messagerie instantanée XMPP", + "service_description_mysql": "Stocke les données des applications (bases de données SQL)", + "service_description_nginx": "Sert ou permet l’accès à tous les sites web hébergés sur votre serveur", + "service_description_nslcd": "Gère la connexion en ligne de commande des utilisateurs YunoHost", "service_description_php5-fpm": "exécute des applications écrites en PHP avec nginx", - "service_description_postfix": "utilisé pour envoyer et recevoir des courriels", + "service_description_postfix": "Utilisé pour envoyer et recevoir des courriels", "service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", - "service_description_rmilter": "vérifie divers paramètres dans les courriels", - "service_description_rspamd": "filtre le pourriel, et d’autres fonctionnalités liées au courriel", - "service_description_slapd": "stocke les utilisateurs, domaines et leurs informations liées", - "service_description_ssh": "vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", - "service_description_yunohost-api": "permet les interactions entre l’interface web de YunoHost et le système", + "service_description_rmilter": "Vérifie divers paramètres dans les courriels", + "service_description_rspamd": "Filtre le pourriel, et d’autres fonctionnalités liées au courriel", + "service_description_slapd": "Stocke les utilisateurs, domaines et leurs informations liées", + "service_description_ssh": "Vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", + "service_description_yunohost-api": "Permet les interactions entre l’interface web de YunoHost et le système", "service_description_yunohost-firewall": "Gère l'ouverture et la fermeture des ports de connexion aux services", "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", "log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}", @@ -466,7 +466,7 @@ "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(", - "migration_0005_not_enough_space": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.", + "migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {chemin} pour exécuter la migration.", "recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.", "service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX", "users_available": "Liste des utilisateurs disponibles :", @@ -557,7 +557,7 @@ "tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …", "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande", "updating_app_lists": "Récupération des mises à jour des applications disponibles…", - "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets).", + "dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets)", "tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques …", "tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", @@ -591,5 +591,49 @@ "mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}", "app_action_broke_system": "Cette action semble avoir cassé des services important : {services}", "apps_already_up_to_date": "Toutes les applications sont déjà à jour", - "app_upgrade_stopped": "La mise à jour de toutes les applications a été arrêtée afin d’éviter d’éventuels dommages dus à l’échec de la mise à jour de l’application précédente" + "app_upgrade_stopped": "La mise à jour de toutes les applications a été arrêtée afin d’éviter d’éventuels dommages dus à l’échec de la mise à jour de l’application précédente", + "migration_0011_create_group": "Créer un groupe pour chaque utilisateur…", + "migration_0011_done": "Migration réussie. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.", + "migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'", + "migrations_no_such_migration": "Il n'y a pas de migration appelée {id}", + "migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau: {ids}", + "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales", + "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", + "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", + "migration_0011_can_not_backup_before_migration": "Impossible de sauvegarder le système avant la migration. Erreur: {error: s}", + "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", + "migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… essayait de restauration du système.", + "migration_0011_rollback_success": "Système restauré.", + "migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…", + "system_groupname_exists": "Le nom de groupe existe déjà dans le groupe du systèmes", + "tools_update_failed_to_app_fetchlist": "Impossible de mettre à jour les applications de YunoHost car: {error}", + "user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group: s}'", + "user_not_in_group": "L'utilisateur '{user: s}' ne fait pas partie du groupe {group: s}", + "migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", + "permission_not_found": "Autorisation '{permission: s}' non trouvée pour l'application '{app: s}'", + "permission_name_not_valid": "Choisissez un nom d'autorisation autorisé pour '{permission: s}'", + "permission_update_failed": "Impossible de mettre à jour la permission", + "permission_generated": "Base de données des autorisations mise à jour", + "permission_updated": "Permission '{permission: s}' pour l'application '{app: s}' mise à jour", + "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", + "remove_main_permission_not_allowed": "Supprimer l'autorisation principale n'est pas autorisé", + "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur Dyndns {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", + "migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…", + "migrations_already_ran": "Ces migrations sont déjà effectuées: {ids}", + "migrations_dependencies_not_satisfied": "Impossible d'exécuter la migration {id} car vous devez d'abord exécuter ces migrations: {dependencies_id}", + "migrations_failed_to_load_migration": "Impossible de charger la migration {id}: {error}", + "migrations_running_forward": "Exécution de la migration {id}…", + "migrations_success_forward": "Migration {id} terminée", + "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", + "operation_interrupted": "L'opération a été interrompue manuellement", + "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", + "permission_already_exist": "L'autorisation '{permission: s}' pour l'application {app: s} existe déjà", + "permission_created": "Permission '{permission: s}' pour l'application {app: s} créée", + "permission_creation_failed": "Impossible d'accorder la permission", + "permission_deleted": "Permission '{permission: s}' pour app {app: s} supprimée", + "permission_deletion_failed": "Autorisation manquante '{permission: s}' pour supprimer l'application '{app: s}'", + "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", + "migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services", + "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", + "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}" } From 7533dbd6500f9a28fc95905ea458b4da7a7045cf Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 2 Oct 2019 11:49:23 +0000 Subject: [PATCH 66/92] Translated using Weblate (Esperanto) Currently translated at 24.2% (136 of 562 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 157 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 126 insertions(+), 31 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 1a6a2ea9a..1d367260d 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,43 +1,138 @@ { - "admin_password_change_failed": "Malebla ŝanĝi pasvorton", - "admin_password_changed": "Pasvorto de la estro estas ŝanĝita", + "admin_password_change_failed": "Ne eblas ŝanĝi pasvorton", + "admin_password_changed": "La pasvorto de administrado ŝanĝiĝis", "app_already_installed": "{app:s} estas jam instalita", - "app_already_up_to_date": "{app:s} estas ĝisdata", + "app_already_up_to_date": "{app:s} estas jam ĝisdata", "app_argument_required": "Parametro {name:s} estas bezonata", "app_change_url_identical_domains": "Malnovaj kaj novaj domajno/URL estas la sama ('{domain:s}{path:s}'), nenio fareblas.", - "app_change_url_success": "URL de appo {app:s} ŝanĝita al {domain:s}{path:s}", - "app_extraction_failed": "Malebla malkompaktigi instaldosierojn", - "app_id_invalid": "Nevalida apo id", + "app_change_url_success": "{app:s} URL nun estas {domain:s} {path:s}", + "app_extraction_failed": "Ne povis ĉerpi la instalajn dosierojn", + "app_id_invalid": "Nevalida apo ID", "app_incompatible": "Apo {app} ne estas kongrua kun via YunoHost versio", - "app_install_files_invalid": "Nevalidaj instaldosieroj", - "app_location_already_used": "Apo {app} jam estas instalita al tiu loco ({path})", - "user_updated": "Uzanto estas ĝisdatita", + "app_install_files_invalid": "Ĉi tiuj dosieroj ne povas esti instalitaj", + "app_location_already_used": "La app '{app}' jam estas instalita en ({path})", + "user_updated": "Uzantinformoj ŝanĝis", "users_available": "Uzantoj disponeblaj :", "yunohost_already_installed": "YunoHost estas jam instalita", - "yunohost_ca_creation_failed": "Ne eblas krei atestan aŭtoritaton", - "yunohost_ca_creation_success": "Loka atesta aŭtoritato estas kreita.", + "yunohost_ca_creation_failed": "Ne povis krei atestan aŭtoritaton", + "yunohost_ca_creation_success": "Loka atestila aŭtoritato kreiĝis.", "yunohost_installing": "Instalante YunoHost…", - "service_description_glances": "monitoras sisteminformojn de via servilo", - "service_description_metronome": "mastrumas XMPP tujmesaĝilon kontojn", - "service_description_mysql": "stokas aplikaĵojn datojn (SQL datumbazo)", - "service_description_nginx": "servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", - "service_description_nslcd": "mastrumas Yunohost uzantojn konektojn per komanda linio", - "service_description_php7.0-fpm": "rulas aplikaĵojn skibita en PHP kun nginx", - "service_description_postfix": "uzita por sendi kaj ricevi retpoŝtojn", - "service_description_redis-server": "specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", - "service_description_rmilter": "kontrolas diversajn parametrojn en retpoŝtoj", - "service_description_rspamd": "filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto", - "service_description_slapd": "stokas uzantojn, domajnojn kaj rilatajn informojn", - "service_description_ssh": "permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", - "service_description_yunohost-api": "mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", - "service_description_yunohost-firewall": "mastrumas malfermitajn kaj fermitajn konektejojn al servoj", - "service_disable_failed": "Neebla malaktivigi servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}", - "service_disabled": "Servo '{service:s}' estas malaktivigita", + "service_description_glances": "Monitoras sistemajn informojn en via servilo", + "service_description_metronome": "Mastrumas XMPP tujmesaĝilon kontojn", + "service_description_mysql": "Stokas aplikaĵojn datojn (SQL datumbazo)", + "service_description_nginx": "Servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", + "service_description_nslcd": "Mastrumas Yunohost uzantojn konektojn per komanda linio", + "service_description_php7.0-fpm": "Rulas aplikaĵojn skibita en PHP kun nginx", + "service_description_postfix": "Uzita por sendi kaj ricevi retpoŝtojn", + "service_description_redis-server": "Specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", + "service_description_rmilter": "Kontrolas diversajn parametrojn en retpoŝtoj", + "service_description_rspamd": "Filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto", + "service_description_slapd": "Stokas uzantojn, domajnojn kaj rilatajn informojn", + "service_description_ssh": "Permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", + "service_description_yunohost-api": "Mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", + "service_description_yunohost-firewall": "Mastrumas malfermitajn kaj fermitajn konektejojn al servoj", + "service_disable_failed": "Ne povis malŝalti la servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}", + "service_disabled": "'{service: s}' servo malŝaltita", "action_invalid": "Nevalida ago « {action:s} »", "admin_password": "Pasvorto de la estro", "admin_password_too_long": "Bonvolu elekti pasvorton pli mallonga ol 127 signoj", - "already_up_to_date": "Neniu estas farenda! Ĉiu jam estas ĝisdata!", - "app_argument_choice_invalid": "Nevalida elekto por argumento « {name:s} », ĝi devas esti unu el {choices:s}", - "app_argument_invalid": "Nevalida valoro por argumento « {name:s} » : {error:s}", - "app_change_url_failed_nginx_reload": "Reŝargi nginx malsuksesis. Jen la eligo de « nginx -t » :\n{nginx_errors:s}" + "already_up_to_date": "Nenio por fari. Ĉio estas jam ĝisdatigita.", + "app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices:s}' por la argumento '{name:s}'", + "app_argument_invalid": "Elektu validan valoron por la argumento '{name:s}': {error:s}", + "app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors:s}", + "appslist_url_already_tracked": "Estas jam registrita aplika listo kun la URL {url:s}.", + "ask_new_admin_password": "Nova administrada pasvorto", + "app_action_broke_system": "Ĉi tiu ago ŝajne rompis ĉi tiujn gravajn servojn: {services}", + "app_unsupported_remote_type": "Malkontrolita fora speco uzita por la apliko", + "backup_archive_system_part_not_available": "Sistemo parto '{part:s}' ne haveblas en ĉi tiu rezervo", + "apps_permission_not_found": "Neniu permeso trovita por la instalitaj programoj", + "apps_permission_restoration_failed": "Donu la rajtigan permeson '{permission:s}' por restarigi {app:s}", + "backup_abstract_method": "Ĉi tiu rezerva metodo ankoraŭ efektiviĝis", + "apps_already_up_to_date": "Ĉiuj aplikoj estas jam ĝisdatigitaj", + "backup_borg_not_implemented": "La kopia metodo de Borg ankoraŭ ne estas efektivigita", + "app_upgrade_stopped": "La ĝisdatigo de ĉiuj aplikoj estis ĉesigita por eviti eblajn damaĝojn ĉar la antaŭa apliko ne sukcesis ĝisdatigi", + "app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps:s}", + "backup_archive_app_not_found": "Ne povis trovi la programon '{app:s}' en la rezerva ar archiveivo", + "backup_actually_backuping": "Krei rezervan ar archiveivon el la kolektitaj dosieroj …", + "backup_method_borg_finished": "Sekurkopio en Borg finiĝis", + "appslist_removed": "{appslist:s} aplika listo forigita", + "app_change_url_no_script": "Ĉi tiu apliko '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.", + "app_start_install": "Instalanta aplikon {app} …", + "backup_created": "Sekurkopio kreita", + "app_make_default_location_already_used": "Ne povas igi la aplikon '{app}' defaŭlta sur la domajno, {domain} jam uziĝas de la alia app '{other_app}'", + "backup_method_copy_finished": "Rezerva kopio finis", + "app_not_properly_removed": "{app:s} ne estis ĝuste forigita", + "backup_archive_broken_link": "Ne povis aliri la rezervan ar archiveivon (rompita ligilo al {path:s})", + "app_requirements_checking": "Kontrolante postulatajn pakaĵojn por {app} …", + "app_not_installed": "Ne povis trovi la aplikon '{app:s}' en la listo de instalitaj programoj: {all_apps}", + "app_location_install_failed": "Ne eblas instali la aplikon tie ĉar ĝi konfliktas kun la '{other_app}' jam instalita en '{other_path}'", + "ask_new_path": "Nova vojo", + "backup_custom_mount_error": "Propra rezerva metodo ne povis preterpasi la paŝon 'monto'", + "app_upgrade_app_name": "Nun ĝisdatiganta {app} …", + "app_manifest_invalid": "Io misas pri la aplika manifesto: {error}", + "backup_cleaning_failed": "Ne povis purigi la provizoran rezervan dosierujon", + "backup_invalid_archive": "Ĉi tio ne estas rezerva ar archiveivo", + "ask_current_admin_password": "Pasvorto pri aktuala administrado", + "backup_creation_failed": "Ne povis krei la rezervan ar archiveivon", + "backup_hook_unknown": "La rezerva hoko '{hoko:s}' estas nekonata", + "backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"", + "ask_main_domain": "Ĉefa domajno", + "backup_method_tar_finished": "TAR-rezerva ar archiveivo kreita", + "appslist_unknown": "Aplika listo {appslist:s} nekonata.", + "ask_list_to_remove": "Listo por forigi", + "backup_cant_mount_uncompress_archive": "Ne povis munti la nekompresitan ar archiveivon kiel protektita kontraŭ skribo", + "appslist_retrieve_bad_format": "Ne povis legi la elprenitan liston {appslist:s}", + "appslist_corrupted_json": "Ne povis ŝarĝi la aplikajn listojn. Ĝi aspektas kiel {filename:s} estas damaĝita.", + "app_action_cannot_be_ran_because_required_services_down": "Ĉi tiu app postulas iujn servojn, kiuj nuntempe malleviĝas. Antaŭ ol daŭrigi, vi provu rekomenci la jenajn servojn (kaj eventuale esploru kial ili malsukcesas): {services}", + "backup_copying_to_organize_the_archive": "Kopiante {size:s} MB por organizi la ar archiveivon", + "backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj / bin, / boot, / dev, / ktp, / lib, / root, / run, / sbin, / sys, / usr, / var aŭ /home/yunohost.backup/archives", + "appslist_could_not_migrate": "Ne povis migri la liston de aplikoj {appslist:s}! Ne eblis analizi la URL ... La malnova cron-laboro konserviĝis en {bkp_file:s}.", + "app_requirements_failed": "Certaines exigences ne sont pas remplies pour {app}: {error}", + "backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo", + "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", + "app_upgrade_failed": "Ne povis ĝisdatigi {app:s}", + "app_upgrade_several_apps": "La sekvaj apliko estos altgradigitaj: {apps}", + "backup_archive_open_failed": "Ne povis malfermi la rezervan ar archiveivon", + "ask_lastname": "Familia nomo", + "app_start_backup": "Kolekti dosierojn por esti subtenata por {app} …", + "backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.", + "backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …", + "backup_method_custom_finished": "Propra rezerva metodo '{metodo:s}' finiĝis", + "appslist_retrieve_error": "Ne eblas retrovi la forajn aplikajn listojn {appslist:s}: {eraro:s}", + "app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.", + "app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita", + "app_removed": "{app:s} forigita", + "backup_delete_error": "Ne povis forigi '{path: s}'", + "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", + "backup_nothings_done": "Nenio por ŝpari", + "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", + "appslist_fetched": "Ĝisdatigita aplika listo {appslist:s} elprenita", + "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", + "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", + "app_start_remove": "Forigo de apliko {app} …", + "backup_output_directory_not_empty": "Vi devas elekti malplenan eligitan dosierujon", + "backup_archive_writing_error": "Ne povis aldoni la dosierojn '{source:s}' (nomitaj en la ar theivo '{dest:s}') por esti rezervitaj en la kunpremita arkivo '{archive:s}'", + "ask_email": "Retpoŝta adreso", + "app_start_restore": "Restarigi aplikon {app} …", + "backup_applying_method_copy": "Kopiante ĉiujn dosierojn al sekurkopio …", + "backup_couldnt_bind": "Ne povis ligi {src:s} al {dest:s}.", + "ask_password": "Pasvorto", + "app_requirements_unmeet": "Postuloj ne estas renkontitaj por {app}, la pakaĵo {pkgname} ({version}) devas esti {spec}", + "ask_firstname": "Antaŭnomo", + "backup_ask_for_copying_if_needed": "Iuj dosieroj ne povus esti pretigitaj por sekurkopio uzante la metodon, kiu evitas portempe malŝpari spacon en la sistemo. Por plenumi la sekurkopion, {size:s} MB estos provizore. Ĉu vi konsentas?", + "backup_mount_archive_for_restore": "Preparante arkivon por restarigo …", + "appslist_migrating": "Migra aplika listo {appslist:s} …", + "backup_csv_creation_failed": "Ne povis krei la CSV-dosieron bezonatan por restarigo", + "backup_archive_name_unknown": "Nekonata loka rezerva ar archiveivo nomata '{name:s}'", + "backup_applying_method_borg": "Sendado de ĉiuj dosieroj al sekurkopio en borg-rezerva deponejo …", + "app_sources_fetch_failed": "Ne povis akiri fontajn dosierojn, ĉu la URL estas ĝusta?", + "appslist_name_already_tracked": "Registrita aplika listo kun nomo {name:s} jam ekzistas.", + "ask_new_domain": "Nova domajno", + "app_unknown": "Nekonata apliko", + "app_not_upgraded": "La aplikaĵo '{failed_app}' ne ĝisdatigis, kaj pro tio la sekvaj ĝisdatigoj de aplikoj estis nuligitaj: {apps}", + "aborting": "Aborti.", + "ask_path": "Pado", + "app_upgraded": "{app:s} altgradigita", + "backup_deleted": "Rezerva forigita", + "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero" } From a1822e2f42aa1a7ff516ff76ea5dee1def233a20 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Sun, 6 Oct 2019 11:25:01 +0200 Subject: [PATCH 67/92] Use str instead of strerror (not present) See https://forum.yunohost.org/t/cant-create-a-user-after-post-intsallation/9190. --- src/yunohost/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index c6413d7e1..fe27492f4 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -199,7 +199,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) except ValueError as e: - raise YunohostError('ssowat_persistent_conf_read_error', error=e.strerror) + raise YunohostError('ssowat_persistent_conf_read_error', error=str(e)) except IOError: ssowat_conf = {} @@ -209,7 +209,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password, with open('/etc/ssowat/conf.json.persistent', 'w+') as f: json.dump(ssowat_conf, f, sort_keys=True, indent=4) except IOError as e: - raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror) + raise YunohostError('ssowat_persistent_conf_write_error', error=str(e)) try: ldap.add('uid=%s,ou=users' % username, attr_dict) From 2642b64af5bccb6f93a8612a42365edd68e9b118 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Sep 2019 23:17:46 +0200 Subject: [PATCH 68/92] Detect and warn early about unavailable full domain requirement... --- locales/en.json | 1 + src/yunohost/app.py | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 61fdcfa9b..4e9675cac 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,6 +19,7 @@ "app_change_url_no_script": "This application '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", "app_change_url_success": "{app:s} URL is now {domain:s}{path:s}", "app_extraction_failed": "Could not extract the installation files", + "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on {domain}. One possible solution is to add a subdomain dedicated to this application.", "app_id_invalid": "Invalid app ID", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5a51e57bb..6f5c1dabe 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -847,6 +847,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args_list = [ value[0] for value in args_odict.values() ] args_list.append(app_instance_name) + # Validate domain / path availability for webapps + _validate_and_normalize_webpath(manifest, args_odict, extracted_app_folder) + # Prepare env. var. to pass to script env_dict = _make_environment_dict(args_odict) env_dict["YNH_APP_ID"] = app_id @@ -2547,8 +2550,7 @@ def _parse_args_for_action(action, args={}): def _parse_args_in_yunohost_format(args, action_args): """Parse arguments store in either manifest.json or actions.json """ - from yunohost.domain import (domain_list, _get_maindomain, - _get_conflicting_apps, _normalize_domain_path) + from yunohost.domain import domain_list, _get_maindomain from yunohost.user import user_info, user_list args_dict = OrderedDict() @@ -2666,13 +2668,18 @@ def _parse_args_in_yunohost_format(args, action_args): assert_password_is_strong_enough('user', arg_value) args_dict[arg_name] = (arg_value, arg_type) - # END loop over action_args... + return args_dict + + +def _validate_and_normalize_webpath(manifest, args_dict, app_folder): + + from yunohost.domain import _get_conflicting_apps, _normalize_domain_path # If there's only one "domain" and "path", validate that domain/path # is an available url and normalize the path. - domain_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "domain" ] - path_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "path" ] + domain_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "domain"] + path_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "path"] if len(domain_args) == 1 and len(path_args) == 1: @@ -2698,7 +2705,25 @@ def _parse_args_in_yunohost_format(args, action_args): # standard path format to deal with no matter what the user inputted) args_dict[path_args[0][0]] = (path, "path") - return args_dict + # This is likely to be a full-domain app... + elif len(domain_args) == 1 and len(path_args) == 0: + + # Confirm that this is a full-domain app This should cover most cases + # ... though anyway the proper solution is to implement some mechanism + # in the manifest for app to declare that they require a full domain + # (among other thing) so that we can dynamically check/display this + # requirement on the webadmin form and not miserably fail at submit time + + # Full-domain apps typically declare something like path_url="/" or path=/ + # and use ynh_webpath_register or yunohost_app_checkurl inside the install script + install_script_content = open(os.path.join(app_folder, 'scripts/install')).read() + if re.search(r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content) \ + and re.search(r"(ynh_webpath_register|yunohost app checkurl)"): + + domain = domain_args[0][1] + conflicts = _get_conflicting_apps(domain, "/") + + raise YunohostError('app_full_domain_unavailable', domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From 0d90133bb7a0181621824477a855f01256885ae6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:18:27 +0200 Subject: [PATCH 69/92] Improve message --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 4e9675cac..1be7d1151 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,7 +19,7 @@ "app_change_url_no_script": "This application '{app_name:s}' doesn't support URL modification yet. Maybe you should upgrade it.", "app_change_url_success": "{app:s} URL is now {domain:s}{path:s}", "app_extraction_failed": "Could not extract the installation files", - "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on {domain}. One possible solution is to add a subdomain dedicated to this application.", + "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on domain '{domain}'. One possible solution is to add and use a subdomain dedicated to this application instead.", "app_id_invalid": "Invalid app ID", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "These files cannot be installed", From 342fe2d4be0a1300dddf0e747906cb4e16b9b091 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:19:50 +0200 Subject: [PATCH 70/92] Add unit test for full-domain apps --- src/yunohost/tests/test_apps.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 9c85df1e9..fc44ef105 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -36,16 +36,22 @@ def clean(): if _is_installed("legacy_app"): app_remove("legacy_app") + if _is_installed("full_domain_app"): + app_remove("full_domain_app") + to_remove = [] to_remove += glob.glob("/etc/nginx/conf.d/*.d/*legacy*") + to_remove += glob.glob("/etc/nginx/conf.d/*.d/*full_domain*") to_remove += glob.glob("/etc/nginx/conf.d/*.d/*break_yo_system*") for filepath in to_remove: os.remove(filepath) to_remove = [] to_remove += glob.glob("/etc/yunohost/apps/*legacy_app*") + to_remove += glob.glob("/etc/yunohost/apps/*full_domain_app*") to_remove += glob.glob("/etc/yunohost/apps/*break_yo_system*") to_remove += glob.glob("/var/www/*legacy*") + to_remove += glob.glob("/var/www/*full_domain*") for folderpath in to_remove: shutil.rmtree(folderpath, ignore_errors=True) @@ -120,6 +126,13 @@ def install_legacy_app(domain, path): force=True) +def install_full_domain_app(domain): + + app_install("./tests/apps/full_domain_app_ynh", + args="domain=%s" % domain, + force=True) + + def install_break_yo_system(domain, breakwhat): app_install("./tests/apps/break_yo_system_ynh", @@ -272,6 +285,22 @@ def test_legacy_app_failed_remove(secondary_domain): assert app_is_not_installed(secondary_domain, "legacy") +def test_full_domain_app(secondary_domain): + + install_full_domain_app(secondary_domain) + + assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app") + + +def test_full_domain_app_with_conflicts(secondary_domain): + + install_legacy_app(secondary_domain, "/legacy") + + # TODO : once #808 is merged, add test that the message raised is 'app_full_domain_unavailable' + with pytest.raises(YunohostError): + install_full_domain_app(secondary_domain) + + def test_systemfuckedup_during_app_install(secondary_domain): with pytest.raises(YunohostError): From c70418c4b25952f9308b69d88e47b8a2490781c1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:21:04 +0200 Subject: [PATCH 71/92] Fixes following tests --- src/yunohost/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 6f5c1dabe..8596f7297 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2717,13 +2717,14 @@ def _validate_and_normalize_webpath(manifest, args_dict, app_folder): # Full-domain apps typically declare something like path_url="/" or path=/ # and use ynh_webpath_register or yunohost_app_checkurl inside the install script install_script_content = open(os.path.join(app_folder, 'scripts/install')).read() + if re.search(r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content) \ - and re.search(r"(ynh_webpath_register|yunohost app checkurl)"): + and re.search(r"(ynh_webpath_register|yunohost app checkurl)", install_script_content): domain = domain_args[0][1] conflicts = _get_conflicting_apps(domain, "/") - raise YunohostError('app_full_domain_unavailable', domain) + raise YunohostError('app_full_domain_unavailable', domain=domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From fc787009041069f7ffd83b1d7f8467a8d98e1c44 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:30:18 +0200 Subject: [PATCH 72/92] More accurate greps to identify that sury packages are installed --- data/helpers.d/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index f5590b38d..d84520daf 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -226,8 +226,8 @@ ynh_install_app_dependencies () { # If we require to install php dependency if echo $dependencies | grep -q 'php'; then - # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33+1 on debian) - if dpkg --list | grep php | grep -q "7.0.33-10" + # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian) + if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9u5" then # And sury ain't already installed if ! grep -nrq "sury" /etc/apt/sources.list* From 077e5c463c8c4d6befc28d9c0c79bdc21ed2b3cb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 24 Sep 2019 23:18:05 +0200 Subject: [PATCH 73/92] Fucking ugly workaround for the goddamn dependency nighmare from sury djeezus kraiste --- data/helpers.d/apt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 9d5ad3ac2..fed585d95 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -218,6 +218,27 @@ ynh_install_app_dependencies () { fi local dep_app=${app//_/-} # Replace all '_' by '-' + # + # Epic ugly hack to fix the goddamn dependency nightmare of sury + # Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective + # https://github.com/YunoHost/issues/issues/1407 + # + # If we require to install php dependency + if echo $dependencies | grep -q 'php'; + then + # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33+1 on debian) + if dpkg --list | grep php | grep -q "7.0.33-10" + then + # And sury ain't already installed + if ! grep -nrq "sury" /etc/apt/sources.list* + then + # Re-add sury + echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury.list + wget -O /etc/apt/trusted.gpg.d/sury.gpg https://packages.sury.org/php/apt.gpg + fi + fi + fi + cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build Section: misc Priority: optional From 0e3a131095afe4893d10629271c1ce6c6b177624 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:30:18 +0200 Subject: [PATCH 74/92] More accurate greps to identify that sury packages are installed --- data/helpers.d/apt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index fed585d95..d772c6855 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -226,8 +226,8 @@ ynh_install_app_dependencies () { # If we require to install php dependency if echo $dependencies | grep -q 'php'; then - # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33+1 on debian) - if dpkg --list | grep php | grep -q "7.0.33-10" + # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian) + if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9u5" then # And sury ain't already installed if ! grep -nrq "sury" /etc/apt/sources.list* From 1c5220f7cbe029b4cf03011cb451426d755697f8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 26 Sep 2019 14:23:01 +0200 Subject: [PATCH 75/92] Support logfiles not ending with .log in logrotate ... --- data/helpers.d/logrotate | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 47ce46cf6..82cdee6a5 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -40,10 +40,13 @@ ynh_use_logrotate () { fi if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then - if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile - local logfile=$1 # In this case, focus logrotate on the logfile + # If the given logfile parameter already exists as a file, or if it ends up with ".log", + # we just want to manage a single file + if [ -f "$1" ] || [ "$(echo ${1##*.})" == "log" ]; then + local logfile=$1 + # Otherwise we assume we want to manage a directory and all its .log file inside else - local logfile=$1/*.log # Else, uses the directory and all logfile into it. + local logfile=$1/*.log fi fi # LEGACY CODE @@ -54,7 +57,7 @@ ynh_use_logrotate () { fi if [ -n "$logfile" ] then - if [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile + if [ ! -f "$1" ] && [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it. fi else From 5623689a2728a875880dd41b614fffd818a0597d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Sep 2019 23:17:46 +0200 Subject: [PATCH 76/92] Detect and warn early about unavailable full domain requirement... --- locales/en.json | 1 + src/yunohost/app.py | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index d1203c757..55735d760 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,6 +19,7 @@ "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", "app_extraction_failed": "Unable to extract installation files", + "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on {domain}. One possible solution is to add a subdomain dedicated to this application.", "app_id_invalid": "Invalid app id", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d9a349579..c982c3418 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -802,6 +802,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu args_list = [ value[0] for value in args_odict.values() ] args_list.append(app_instance_name) + # Validate domain / path availability for webapps + _validate_and_normalize_webpath(manifest, args_odict, extracted_app_folder) + # Prepare env. var. to pass to script env_dict = _make_environment_dict(args_odict) env_dict["YNH_APP_ID"] = app_id @@ -2394,8 +2397,7 @@ def _parse_args_for_action(action, args={}): def _parse_args_in_yunohost_format(args, action_args): """Parse arguments store in either manifest.json or actions.json """ - from yunohost.domain import (domain_list, _get_maindomain, - _get_conflicting_apps, _normalize_domain_path) + from yunohost.domain import domain_list, _get_maindomain from yunohost.user import user_info, user_list args_dict = OrderedDict() @@ -2513,13 +2515,18 @@ def _parse_args_in_yunohost_format(args, action_args): assert_password_is_strong_enough('user', arg_value) args_dict[arg_name] = (arg_value, arg_type) - # END loop over action_args... + return args_dict + + +def _validate_and_normalize_webpath(manifest, args_dict, app_folder): + + from yunohost.domain import _get_conflicting_apps, _normalize_domain_path # If there's only one "domain" and "path", validate that domain/path # is an available url and normalize the path. - domain_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "domain" ] - path_args = [ (name, value[0]) for name, value in args_dict.items() if value[1] == "path" ] + domain_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "domain"] + path_args = [(name, value[0]) for name, value in args_dict.items() if value[1] == "path"] if len(domain_args) == 1 and len(path_args) == 1: @@ -2545,7 +2552,25 @@ def _parse_args_in_yunohost_format(args, action_args): # standard path format to deal with no matter what the user inputted) args_dict[path_args[0][0]] = (path, "path") - return args_dict + # This is likely to be a full-domain app... + elif len(domain_args) == 1 and len(path_args) == 0: + + # Confirm that this is a full-domain app This should cover most cases + # ... though anyway the proper solution is to implement some mechanism + # in the manifest for app to declare that they require a full domain + # (among other thing) so that we can dynamically check/display this + # requirement on the webadmin form and not miserably fail at submit time + + # Full-domain apps typically declare something like path_url="/" or path=/ + # and use ynh_webpath_register or yunohost_app_checkurl inside the install script + install_script_content = open(os.path.join(app_folder, 'scripts/install')).read() + if re.search(r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content) \ + and re.search(r"(ynh_webpath_register|yunohost app checkurl)"): + + domain = domain_args[0][1] + conflicts = _get_conflicting_apps(domain, "/") + + raise YunohostError('app_full_domain_unavailable', domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From 75742216ea93e45c6679310fa9a02724775dd838 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:18:27 +0200 Subject: [PATCH 77/92] Improve message --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 55735d760..4bb049db3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,7 +19,7 @@ "app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.", "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", "app_extraction_failed": "Unable to extract installation files", - "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on {domain}. One possible solution is to add a subdomain dedicated to this application.", + "app_full_domain_unavailable": "Sorry, this application requires a full domain to be installed on, but some other apps are already installed on domain '{domain}'. One possible solution is to add and use a subdomain dedicated to this application instead.", "app_id_invalid": "Invalid app id", "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", From 7ecefaf8dc78cc0d4ddb1f0fabc5eab6ff2bb176 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 18:21:04 +0200 Subject: [PATCH 78/92] Fixes following tests --- src/yunohost/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c982c3418..421be9b60 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2564,13 +2564,14 @@ def _validate_and_normalize_webpath(manifest, args_dict, app_folder): # Full-domain apps typically declare something like path_url="/" or path=/ # and use ynh_webpath_register or yunohost_app_checkurl inside the install script install_script_content = open(os.path.join(app_folder, 'scripts/install')).read() + if re.search(r"\npath(_url)?=[\"']?/[\"']?\n", install_script_content) \ - and re.search(r"(ynh_webpath_register|yunohost app checkurl)"): + and re.search(r"(ynh_webpath_register|yunohost app checkurl)", install_script_content): domain = domain_args[0][1] conflicts = _get_conflicting_apps(domain, "/") - raise YunohostError('app_full_domain_unavailable', domain) + raise YunohostError('app_full_domain_unavailable', domain=domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From bf1ad164dafc996c0bf9d1a3b19de7dc2d089003 Mon Sep 17 00:00:00 2001 From: "J. Doe" Date: Thu, 19 Sep 2019 13:01:22 +0200 Subject: [PATCH 79/92] change maxretry of fail2ban from 6 to 10 --- data/templates/fail2ban/yunohost-jails.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/fail2ban/yunohost-jails.conf b/data/templates/fail2ban/yunohost-jails.conf index bf3bcb6e3..fdbd7990b 100644 --- a/data/templates/fail2ban/yunohost-jails.conf +++ b/data/templates/fail2ban/yunohost-jails.conf @@ -29,4 +29,4 @@ protocol = tcp filter = yunohost logpath = /var/log/nginx/*error.log /var/log/nginx/*access.log -maxretry = 6 +maxretry = 10 From 115513c6503de63a7f970d1f2dae216839b7f46d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 19:03:32 +0200 Subject: [PATCH 80/92] Update changelog for 3.6.5 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 3eb347456..4b8c26471 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (3.6.5) stable; urgency=low + + - [enh] Detect and warn early about unavailable full domains... (#798) + - [mod] Change maxretry of fail2ban from 6 to 10 (#802) + - [fix] Epicly ugly workaround for the goddamn dependency nighmare about sury fucking up php7.0 dependencies (#809) + - [fix] Support logfiles not ending with .log in logrotate ... (#810) + + -- Alexandre Aubin Mon, 08 Oct 2019 19:00:00 +0000 + yunohost (3.6.4.6) stable; urgency=low - [fix] Hopefully fix the issue about corrupted logs metadata files (d507d447, 1cec9d78) From fe8fd1b2c58993211e016fecb74d6fc026482bd1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 20:04:08 +0200 Subject: [PATCH 81/92] Change from #802 was only about the yunohost jail ... this should be global >.> --- data/templates/fail2ban/jail.conf | 2 +- data/templates/fail2ban/yunohost-jails.conf | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/data/templates/fail2ban/jail.conf b/data/templates/fail2ban/jail.conf index 9b4d39f17..bd522c4ba 100644 --- a/data/templates/fail2ban/jail.conf +++ b/data/templates/fail2ban/jail.conf @@ -63,7 +63,7 @@ bantime = 600 findtime = 600 # "maxretry" is the number of failures before a host get banned. -maxretry = 5 +maxretry = 10 # "backend" specifies the backend used to get files modification. # Available options are "pyinotify", "gamin", "polling", "systemd" and "auto". diff --git a/data/templates/fail2ban/yunohost-jails.conf b/data/templates/fail2ban/yunohost-jails.conf index fdbd7990b..e1e464b1a 100644 --- a/data/templates/fail2ban/yunohost-jails.conf +++ b/data/templates/fail2ban/yunohost-jails.conf @@ -29,4 +29,3 @@ protocol = tcp filter = yunohost logpath = /var/log/nginx/*error.log /var/log/nginx/*access.log -maxretry = 10 From 826429cf0b5c8dc72cb2c3898c42cf0f20971cb4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 20:04:08 +0200 Subject: [PATCH 82/92] Change from #802 was only about the yunohost jail ... this should be global >.> --- data/templates/fail2ban/jail.conf | 2 +- data/templates/fail2ban/yunohost-jails.conf | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/data/templates/fail2ban/jail.conf b/data/templates/fail2ban/jail.conf index 9b4d39f17..bd522c4ba 100644 --- a/data/templates/fail2ban/jail.conf +++ b/data/templates/fail2ban/jail.conf @@ -63,7 +63,7 @@ bantime = 600 findtime = 600 # "maxretry" is the number of failures before a host get banned. -maxretry = 5 +maxretry = 10 # "backend" specifies the backend used to get files modification. # Available options are "pyinotify", "gamin", "polling", "systemd" and "auto". diff --git a/data/templates/fail2ban/yunohost-jails.conf b/data/templates/fail2ban/yunohost-jails.conf index fdbd7990b..e1e464b1a 100644 --- a/data/templates/fail2ban/yunohost-jails.conf +++ b/data/templates/fail2ban/yunohost-jails.conf @@ -29,4 +29,3 @@ protocol = tcp filter = yunohost logpath = /var/log/nginx/*error.log /var/log/nginx/*access.log -maxretry = 10 From c45b0edd39e47d66201c9b9223923abfd93f3a58 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Oct 2019 20:21:11 +0200 Subject: [PATCH 83/92] Update changelog for 3.6.5.1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 4b8c26471..6d5a16f2c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.6.5.1) stable; urgency=low + + - [mod] Change maxretry of fail2ban from 6 to 10 (fe8fd1b) + + -- Alexandre Aubin Mon, 08 Oct 2019 20:00:00 +0000 + yunohost (3.6.5) stable; urgency=low - [enh] Detect and warn early about unavailable full domains... (#798) From 2623d38567d18980265b8b30cdf1786f89355ad5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Oct 2019 00:06:36 +0200 Subject: [PATCH 84/92] Annnnnd Alex was drunk and released an epic stupid bug in stable --- src/yunohost/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 421be9b60..88204f3ff 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2569,9 +2569,8 @@ def _validate_and_normalize_webpath(manifest, args_dict, app_folder): and re.search(r"(ynh_webpath_register|yunohost app checkurl)", install_script_content): domain = domain_args[0][1] - conflicts = _get_conflicting_apps(domain, "/") - - raise YunohostError('app_full_domain_unavailable', domain=domain) + if _get_conflicting_apps(domain, "/"): + raise YunohostError('app_full_domain_unavailable', domain=domain) def _make_environment_dict(args_dict, prefix="APP_ARG_"): From f2db3d34dd21fdf8d170fbd3eaea42ddb11283c9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 10 Oct 2019 00:08:47 +0200 Subject: [PATCH 85/92] Update changelog for 3.6.5.2 --- debian/changelog | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 6d5a16f2c..1d13b6290 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ +yunohost (3.6.5.2) stable; urgency=low + + - [fix] Alex was drunk and released an epic stupid bug in stable (2623d385) + + -- Alexandre Aubin Thu, 10 Oct 2019 01:00:00 +0000 + yunohost (3.6.5.1) stable; urgency=low - [mod] Change maxretry of fail2ban from 6 to 10 (fe8fd1b) - -- Alexandre Aubin Mon, 08 Oct 2019 20:00:00 +0000 + -- Alexandre Aubin Tue, 08 Oct 2019 20:00:00 +0000 yunohost (3.6.5) stable; urgency=low @@ -11,7 +17,7 @@ yunohost (3.6.5) stable; urgency=low - [fix] Epicly ugly workaround for the goddamn dependency nighmare about sury fucking up php7.0 dependencies (#809) - [fix] Support logfiles not ending with .log in logrotate ... (#810) - -- Alexandre Aubin Mon, 08 Oct 2019 19:00:00 +0000 + -- Alexandre Aubin Tue, 08 Oct 2019 19:00:00 +0000 yunohost (3.6.4.6) stable; urgency=low From 38fd969f94c90a64e9119ee3b78c8f2c62c169e0 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 7 Oct 2019 05:19:53 +0000 Subject: [PATCH 86/92] Translated using Weblate (Esperanto) Currently translated at 100.0% (553 of 553 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 422 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 420 insertions(+), 2 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 1d367260d..d27c0171c 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -106,7 +106,7 @@ "app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn", "backup_nothings_done": "Nenio por ŝpari", "backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …", - "appslist_fetched": "Ĝisdatigita aplika listo {appslist:s} elprenita", + "appslist_fetched": "Ĝisdatigita aplika listo {appslist:s}", "backup_app_failed": "Ne eblis rezervi la programon '{app:s}'", "app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj", "app_start_remove": "Forigo de apliko {app} …", @@ -134,5 +134,423 @@ "ask_path": "Pado", "app_upgraded": "{app:s} altgradigita", "backup_deleted": "Rezerva forigita", - "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero" + "backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero", + "dpkg_lock_not_available": "Ĉi tiu komando ne povas funkcii nun ĉar alia programo uzas la seruron de dpkg (la administrilo de paka sistemo)", + "migration_0003_yunohost_upgrade": "Komenci la ĝisdatigon de YunoHost-pako ... La migrado finiĝos, sed la efektiva ĝisdatigo okazos tuj poste. Post kiam la operacio finiĝos, vi eble devos ensaluti denove sur la retpaĝo.", + "domain_dyndns_root_unknown": "Nekonata radika domajno DynDNS", + "field_invalid": "Nevalida kampo '{:s}'", + "log_app_makedefault": "Faru '{}' la defaŭlta apliko", + "migration_0003_still_on_jessie_after_main_upgrade": "Io okazis malbone dum la ĉefa ĝisdatigo: Ĉu la sistemo ankoraŭ estas en Jessie‽ Por esplori la aferon, bonvolu rigardi {log}:s …", + "migration_0011_can_not_backup_before_migration": "La sekurkopio de la sistemo antaŭ la migrado malsukcesis. Migrado malsukcesis. Eraro: {error:s}", + "migration_0011_create_group": "Krei grupon por ĉiu uzanto…", + "backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part:s}'", + "global_settings_setting_security_postfix_compatibility": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", + "group_unknown": "La grupo '{group:s}' estas nekonata", + "mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user:s}", + "migration_description_0011_setup_group_permission": "Agordu uzantogrupon kaj starigu permeson por programoj kaj servoj", + "migration_0011_backup_before_migration": "Krei sekurkopion de LDAP-datumbazo kaj agordojn antaŭ la efektiva migrado.", + "migration_0011_LDAP_config_dirty": "Similas ke vi agordis vian LDAP-agordon. Por ĉi tiu migrado la LDAP-agordo bezonas esti ĝisdatigita.\nVi devas konservi vian aktualan agordon, reintaligi la originalan agordon per funkciado de \"yunohost iloj regen-conf -f\" kaj reprovi la migradon", + "migration_0011_migrate_permission": "Migrado de permesoj de agordoj al aplikoj al LDAP…", + "migration_0011_migration_failed_trying_to_rollback": "Migrado malsukcesis ... provante reverti la sistemon.", + "migrations_dependencies_not_satisfied": "Ne eblas kuri migradon {id} ĉar unue vi devas ruli ĉi tiujn migradojn: {dependencies_id}", + "migrations_failed_to_load_migration": "Ne povis ŝarĝi migradon {id}: {error}", + "migrations_exclusive_options": "'--auto', '--skip' kaj '--force-rerun' estas reciproke ekskluzivaj ebloj.", + "migrations_must_provide_explicit_targets": "Vi devas provizi eksplicitajn celojn kiam vi uzas '--skip' aŭ '--force-rerun'", + "permission_update_failed": "Ne povis ĝisdatigi permeson '{permission}': {error}", + "permission_updated": "Ĝisdatigita \"{permission:s}\" rajtigita", + "permission_update_nothing_to_do": "Neniuj permesoj ĝisdatigi", + "tools_upgrade_cant_hold_critical_packages": "Ne povis teni kritikajn pakojn…", + "upnp_dev_not_found": "Neniu UPnP-aparato trovita", + "migration_description_0012_postgresql_password_to_md5_authentication": "Devigu PostgreSQL-aŭtentigon uzi MD5 por lokaj ligoj", + "migration_0011_done": "Migrado sukcesis. Vi nun kapablas administri uzantajn grupojn.", + "migration_0011_LDAP_update_failed": "Ne povis ĝisdatigi LDAP. Eraro: {error:s}", + "pattern_password": "Devas esti almenaŭ 3 signoj longaj", + "root_password_desynchronized": "La pasvorta administranto estis ŝanĝita, sed YunoHost ne povis propagandi ĉi tion al la radika pasvorto!", + "service_remove_failed": "Ne povis forigi la servon '{service:s}'", + "migration_0003_fail2ban_upgrade": "Komenci la ĝisdatigon Fail2Ban…", + "backup_permission": "Rezerva permeso por app {app:s}", + "log_user_group_delete": "Forigi grupon '{}'", + "log_user_group_update": "Ĝisdatigi grupon '{}'", + "migration_0005_postgresql_94_not_installed": "PostgreSQL ne estis instalita en via sistemo. Nenio por fari.", + "dyndns_provider_unreachable": "Ne povas atingi Dyndns-provizanton {provider}: ĉu via YunoHost ne estas ĝuste konektita al la interreto aŭ la dynette-servilo malŝaltiĝas.", + "good_practices_about_user_password": "Vi nun estas por difini novan uzantan pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", + "group_updated": "Ĝisdatigita \"{group}\" grupo", + "group_already_exist": "Grupo {group} jam ekzistas", + "group_already_exist_on_system": "Grupo {group} jam ekzistas en la sistemaj grupoj", + "group_cannot_be_edited": "La grupo {group} ne povas esti redaktita permane.", + "group_cannot_be_deleted": "La grupo {group} ne povas esti forigita permane.", + "group_update_failed": "Ne povis ĝisdatigi la grupon '{group}': {error}", + "group_user_already_in_group": "Uzanto {user} jam estas en grupo {group}", + "group_user_not_in_group": "Uzanto {user} ne estas en grupo {group}", + "installation_complete": "Kompleta instalado", + "log_category_404": "La loga kategorio '{category}' ne ekzistas", + "log_permission_create": "Krei permeson '{}'", + "log_permission_delete": "Forigi permeson '{}'", + "log_permission_urls": "Ĝisdatigu URLojn rilatajn al permeso '{}'", + "log_user_group_create": "Krei grupon '{}'", + "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", + "log_user_permission_reset": "Restarigi permeson '{}'", + "mail_forward_remove_failed": "Ne povis forigi retpoŝton plusendante '{mail:s}'", + "migration_0011_rollback_success": "Sistemo ruliĝis reen.", + "migration_0011_update_LDAP_database": "Ĝisdatigante LDAP-datumbazon…", + "migration_0011_update_LDAP_schema": "Ĝisdatigante LDAP-skemon…", + "migration_0011_failed_to_remove_stale_object": "Malsukcesis forigi neokazan objekton {dn}: {error}", + "migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}", + "migrations_no_such_migration": "Estas neniu migrado nomata {id}", + "permission_already_allowed": "Grupo '{group}' jam havas permeson '{permission}' ebligita'", + "permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'", + "permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas", + "permission_creation_failed": "Ne povis krei permeson '{permission}': {error}", + "tools_update_failed_to_app_fetchlist": "Ne povis ĝisdatigi la aparatojn de YunoHost ĉar: {error}", + "user_already_exists": "Uzanto {uzanto} jam ekzistas", + "migrations_pending_cant_rerun": "Tiuj migradoj ankoraŭ estas pritraktataj, do ne plu rajtas esti ekzekutitaj: {ids}", + "migrations_running_forward": "Kuranta migrado {id}…", + "migrations_success_forward": "Migrado {id} kompletigita", + "operation_interrupted": "La operacio estis permane interrompita?", + "permission_created": "Permesita '{permission:s}' kreita", + "permission_deleted": "Permesita \"{permission:s}\" forigita", + "permission_deletion_failed": "Ne povis forigi permeson '{permission}': {error}", + "permission_not_found": "Permesita \"{permission:s}\" ne trovita", + "restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", + "tools_upgrade_regular_packages": "Nun ĝisdatigi 'regulajn' (ne-yunohost-rilatajn) pakojn …", + "tools_upgrade_special_packages_explanation": "Ĉi tiu ago finiĝos, sed la fakta speciala ĝisdatigo daŭros en fono. Bonvolu ne komenci iun alian agon en via servilo en la sekvaj ~ 10 minutoj (depende de via aparata rapideco). Unufoje mi plenumis, vi eble devos ensaluti en la retpaĝo. La ĝisdatiga registro estos havebla en Iloj → Madero (sur la retpaĝo) aŭ tra 'yunohost-registro-listo' (el la komandlinio).", + "unrestore_app": "App '{app:s}' ne restarigos", + "group_created": "Grupo '{group}' kreita", + "group_creation_failed": "Ne povis krei la grupon '{group}': {error}", + "group_deleted": "Grupo '{group}' forigita", + "group_deletion_failed": "Ne povis forigi la grupon '{group}': {error}", + "migrations_not_pending_cant_skip": "Tiuj migradoj ankoraŭ ne estas pritraktataj, do ne eblas preterlasi: {ids}", + "permission_already_exist": "Permesita '{permission}' jam ekzistas", + "domain_created": "Domajno kreita", + "migrate_tsig_wait_2": "2 minutoj …", + "log_user_create": "Aldonu uzanton '{}'", + "ip6tables_unavailable": "Vi ne povas ludi kun ip6tabloj ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", + "mail_unavailable": "Ĉi tiu retpoŝta adreso estas rezervita kaj aŭtomate estos atribuita al la unua uzanto", + "certmanager_domain_dns_ip_differs_from_public_ip": "La DNS 'A' rekordo por la domajno '{domain:s}' diferencas de ĉi tiu IP-servilo. Se vi lastatempe modifis vian A-registron, bonvolu atendi ĝin propagandi (iuj DNS-disvastigaj kontroliloj estas disponeblaj interrete). (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", + "tools_upgrade_special_packages_completed": "Plenumis la ĝisdatigon de pakaĵoj de YunoHost.\nPremu [Enter] por retrovi la komandlinion", + "log_remove_on_failed_install": "Forigu '{}' post malsukcesa instalado", + "regenconf_file_manually_modified": "La agorddosiero '{conf}' estis modifita permane kaj ne estos ĝisdatigita", + "regenconf_would_be_updated": "La agordo estus aktualigita por la kategorio '{category}'", + "certmanager_cert_install_success_selfsigned": "Mem-subskribita atestilo nun instalita por la domajno '{domain:s}'", + "global_settings_unknown_setting_from_settings_file": "Nekonata ŝlosilo en agordoj: '{setting_key:s}', forĵetu ĝin kaj konservu ĝin en /etc/yunohost/settings-unknown.json", + "regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'", + "migration_0007_cannot_restart": "SSH ne rekomencas post provi nuligi la migradan numeron 6.", + "migration_description_0006_sync_admin_and_root_passwords": "Sinkronigu admin kaj radikajn pasvortojn", + "updating_app_lists": "Akirante haveblajn ĝisdatigojn por aplikoj…", + "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", + "global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}", + "service_added": "La servo '{service:s}' aldonis", + "upnp_disabled": "UPnP malŝaltis", + "service_started": "Servo '{service:s}' komenciĝis", + "port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version:s} rilatoj", + "installation_failed": "Io okazis malbone kun la instalado", + "network_check_mx_ko": "DNS MX-rekordo ne estas agordita", + "migrate_tsig_wait_3": "1 minuto …", + "certmanager_conflicting_nginx_file": "Ne povis prepari domajnon por ACME-defio: la agordo de NGINX {filepath:s} konfliktas kaj unue devas esti forigita", + "upgrading_packages": "Ĝisdatigi pakojn…", + "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app: s}", + "service_reload_failed": "Ne povis reŝargi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "packages_upgrade_failed": "Ne povis ĝisdatigi ĉiujn pakojn", + "hook_json_return_error": "Ne povis legi revenon de hoko {path:s}. Eraro: {msg:s}. Kruda enhavo: {raw_content}", + "dyndns_cron_removed": "DynDNS cron-laboro forigita", + "dyndns_key_not_found": "DNS-ŝlosilo ne trovita por la domajno", + "custom_appslist_name_required": "Vi devas doni nomon por via kutima app-listo", + "tools_upgrade_regular_packages_failed": "Ne povis ĝisdatigi pakojn: {packages_list}", + "service_start_failed": "Ne povis komenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "service_reloaded": "Servo '{service:s}' reŝargita", + "system_upgraded": "Sistemo ĝisdatigita", + "domain_deleted": "Domajno forigita", + "certmanager_acme_not_configured_for_domain": "Atestilo por la domajno '{domain:s}' ne ŝajnas esti ĝuste instalita. Bonvolu ekzekuti 'cert-instali' por ĉi tiu regado unue.", + "migration_description_0009_decouple_regenconf_from_services": "Malkonstruu la regen-konf-mekanismon de servoj", + "user_update_failed": "Ne povis ĝisdatigi uzanton {user}: {error}", + "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 2, manlibro)", + "restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers:s}]", + "pattern_positive_number": "Devas esti pozitiva nombro", + "monitor_stats_file_not_found": "Statistika dosiero ne trovita", + "certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", + "update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", + "migrations_no_migrations_to_run": "Neniuj migradoj por funkcii", + "executing_command": "Plenumanta komandon '{command:s}' …", + "diagnosis_no_apps": "Neniu instalita apliko", + "certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain:s}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!", + "global_settings_setting_example_bool": "Ekzemplo bulea elekto", + "domain_dyndns_already_subscribed": "Vi jam abonis DynDNS-domajnon", + "log_letsencrypt_cert_renew": "Renovigu '{}' Ni ĉifru atestilon", + "migrate_tsig_start": "Detektita ŝlosila algoritmo nesufiĉa por TSIG-subskribo de la domajno '{domain}', komencanta migradon al la pli sekura HMAC-SHA-512", + "ldap_init_failed_to_create_admin": "LDAP-iniciato ne povis krei administran uzanton", + "backup_output_directory_required": "Vi devas provizi elirejan dosierujon por la sekurkopio", + "tools_upgrade_cant_unhold_critical_packages": "Ne povis malhelpi kritikajn pakojn…", + "diagnosis_monitor_disk_error": "Ne povis monitori diskojn: {error}", + "log_link_to_log": "Plena ŝtipo de ĉi tiu operacio: ' {desc} '", + "service_no_log": "Neniu registro por montri por servo '{service:s}'", + "global_settings_cant_serialize_settings": "Ne eblis serialigi datumojn pri agordoj, motivo: {reason:s}", + "backup_running_hooks": "Kurado de apogaj hokoj …", + "package_not_installed": "Pako '{pkgname}' ne estas instalita", + "certmanager_domain_unknown": "Nekonata domajno '{domain:s}'", + "unexpected_error": "Io neatendita iris malbone: {error}", + "password_listed": "Ĉi tiu pasvorto estas inter la plej uzataj pasvortoj en la mondo. Bonvolu elekti ion pli unikan.", + "ssowat_persistent_conf_write_error": "Ne povis konservi konstantan SSOwat-agordon: {error:s}. Redakti /etc/ssowat/conf.json.persistent dosiero por ripari la Jaks-sintakson", + "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 1, aŭtomata)", + "migration_0009_not_needed": "Ĉi tiu migrado jam iel okazis ... (?) Saltado.", + "ssowat_conf_generated": "SSOwat-agordo generita", + "migrate_tsig_wait": "Atendante tri minutojn por ke la servilo DynDNS enkalkulu la novan ŝlosilon …", + "log_remove_on_failed_restore": "Forigu '{}' post malsukcesa restarigo de rezerva ar archiveivo", + "dpkg_is_broken": "Vi ne povas fari ĉi tion nun ĉar dpkg/APT (la administrantoj pri pakaĵaj sistemoj) ŝajnas esti rompita stato ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", + "recommend_to_add_first_user": "La postinstalo finiĝis, sed YunoHost bezonas almenaŭ unu uzanton por funkcii ĝuste, vi devas aldoni unu uzante 'yunohost user create ' aŭ fari ĝin de la administra interfaco.", + "certmanager_cert_signing_failed": "Ne povis subskribi la novan atestilon", + "migration_description_0003_migrate_to_stretch": "Altgradigu la sistemon al Debian Stretch kaj YunoHost 3.0", + "log_tools_upgrade": "Ĝisdatigu sistemajn pakaĵojn", + "network_check_smtp_ko": "Ekstera retpoŝto (SMTP-haveno 25) ŝajnas esti blokita de via reto", + "log_available_on_yunopaste": "Ĉi tiu protokolo nun haveblas per {url}", + "certmanager_http_check_timeout": "Ekdifinita kiam servilo provis kontakti sin per HTTP per publika IP-adreso (domajno '{domain:s}' kun IP '{ip:s}'). Vi eble spertas haŭtoproblemon, aŭ la fajroŝirmilo / enkursigilo antaŭ via servilo miskonfiguras.", + "pattern_port_or_range": "Devas esti valida haveno-nombro (t.e. 0-65535) aŭ gamo da havenoj (t.e. 100:200)", + "migrations_loading_migration": "Ŝarĝante migradon {id}…", + "port_available": "Haveno {port:d} estas havebla", + "pattern_mailbox_quota": "Devas esti grandeco kun la sufikso b/k/M/G/T aŭ 0 por ne havi kvoton", + "migration_0008_general_disclaimer": "Por plibonigi la sekurecon de via servilo, rekomendas lasi YunoHost administri la SSH-agordon. Via nuna SSH-aranĝo diferencas de la rekomendo. Se vi lasas YunoHost agordi ĝin, la maniero per kiu vi konektas al via servilo per SSH ŝanĝiĝos tiel:", + "user_deletion_failed": "Ne povis forigi uzanton {user}: {error}", + "backup_with_no_backup_script_for_app": "La app '{app:s}' ne havas sekretan skripton. Ignorante.", + "service_regen_conf_is_deprecated": "'yunohost service regen-conf' malakceptas! Bonvolu uzi anstataŭe 'yunohost tools regen-conf'.", + "global_settings_key_doesnt_exists": "La ŝlosilo '{settings_key:s}' ne ekzistas en la tutmondaj agordoj, vi povas vidi ĉiujn disponeblajn klavojn per uzado de 'yunohost settings list'", + "dyndns_no_domain_registered": "Neniu domajno registrita ĉe DynDNS", + "dyndns_could_not_check_available": "Ne povis kontroli ĉu {domain:s} haveblas sur {provider:s}.", + "log_app_removelist": "Forigu aplikan liston", + "global_settings_setting_example_enum": "Ekzemplo enum elekto", + "hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}", + "service_stopped": "'{service:s}' servo ĉesis", + "restore_failed": "Ne povis restarigi sistemon", + "confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", + "log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste", + "upgrade_complete": "Ĝisdatigo kompleta", + "upnp_enabled": "UPnP ŝaltis", + "mailbox_used_space_dovecot_down": "La retpoŝta servo de Dovecot devas funkcii, se vi volas akcepti uzitan poŝtan spacon", + "restore_system_part_failed": "Ne povis restarigi la sisteman parton '{part:s}'", + "diagnosis_monitor_system_error": "Ne povis monitori sistemon: {error}", + "service_stop_failed": "Ne povis maldaŭrigi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "unbackup_app": "App '{app:s}' ne konserviĝos", + "updating_apt_cache": "Akirante haveblajn ĝisdatigojn por sistemaj pakoj…", + "tools_upgrade_at_least_one": "Bonvolu specifi '--apps' aŭ '--system'", + "service_already_stopped": "La servo '{service:s}' jam ĉesis", + "unit_unknown": "Nekonata unuo '{unit:s}'", + "migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manual_modified_files}", + "tools_upgrade_cant_both": "Ne eblas ĝisdatigi ambaŭ sistemon kaj programojn samtempe", + "restore_extracting": "Eltirante bezonatajn dosierojn el la ar theivo…", + "upnp_port_open_failed": "Ne povis malfermi havenon per UPnP", + "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", + "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log display {name} --share' por akiri helpon", + "migration_description_0002_migrate_to_tsig_sha256": "Plibonigu sekurecon de DynDNS TSIG-ĝisdatigoj per SHA-512 anstataŭ MD5", + "monitor_disabled": "Servila monitorado nun malŝaltis", + "pattern_port": "Devas esti valida havena numero (t.e. 0-65535)", + "port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version:s} rilatoj", + "hook_name_unknown": "Nekonata hoko-nomo '{name:s}'", + "migration_0003_system_not_fully_up_to_date": "Via sistemo ne estas plene ĝisdata. Bonvolu plenumi regulan ĝisdatigon antaŭ ol ruli la migradon al Stretch.", + "dyndns_could_not_check_provide": "Ne povis kontroli ĉu {provider:s} povas provizi {domain:s}.", + "dyndns_cron_remove_failed": "Ne povis forigi la cron-laboron DynDNS ĉar: {error}", + "pattern_listname": "Devas esti nur alfanumeraj kaj substrekaj signoj", + "restore_nothings_done": "Nenio estis restarigita", + "log_tools_postinstall": "Afiŝu vian servilon YunoHost", + "dyndns_unavailable": "La domajno '{domain:s}' ne haveblas.", + "experimental_feature": "Averto: Ĉi tiu funkcio estas eksperimenta kaj ne konsiderata stabila, vi ne uzu ĝin krom se vi scias kion vi faras.", + "root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.", + "ssowat_persistent_conf_read_error": "Ne povis legi konstantan SSOwat-agordon: {error:s}. Redakti /etc/ssowat/conf.json.persistent dosiero por ripari la Jaks-sintakson", + "migration_description_0005_postgresql_9p4_to_9p6": "Migru datumbazojn de PostgreSQL 9.4 al 9.6", + "migration_0008_root": "• Vi ne povos konekti kiel radiko per SSH. Anstataŭe vi uzu la administran uzanton;", + "package_unknown": "Nekonata pako '{pkgname}'", + "domain_unknown": "Nekonata domajno", + "global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto", + "restore_may_be_not_enough_disk_space": "Via sistemo ŝajnas ne havi sufiĉe da spaco (free:{free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)", + "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", + "downloading": "Elŝutante …", + "user_deleted": "Uzanto forigita", + "service_enable_failed": "Ne eblis ŝalti la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "tools_upgrade_special_packages": "Nun ĝisdatigi 'specialajn' (rilatajn al yunohost)…", + "domains_available": "Haveblaj domajnoj:", + "dyndns_registered": "Registrita domajno DynDNS", + "service_description_fail2ban": "Protektas kontraŭ bruta forto kaj aliaj specoj de atakoj de la interreto", + "file_does_not_exist": "La dosiero {path:s} ne ekzistas.", + "yunohost_not_installed": "YunoHost estas malĝuste aŭ ne ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 estas instalita, sed ne postgresql 9.6‽ Io stranga eble okazis en via sistemo: (…", + "restore_removing_tmp_dir_failed": "Ne povis forigi malnovan provizoran dosierujon", + "certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain:s} (dosiero: {file:s}), kialo: {reason:s}", + "service_removed": "'{service:s}' servo forigita", + "certmanager_hit_rate_limit": "Tro multaj atestiloj jam eldonitaj por ĉi tiu ĝusta aro de domajnoj {domain:s} antaŭ nelonge. Bonvolu reprovi poste. Vidu https://letsencrypt.org/docs/rate-limits/ por pliaj detaloj", + "migration_0005_not_enough_space": "Disponigu sufiĉan spacon en {path} por ruli la migradon.", + "pattern_firstname": "Devas esti valida antaŭnomo", + "migration_description_0010_migrate_to_apps_json": "Forigu malvalorigitajn aparatojn kaj uzu anstataŭe la novan unuigitan liston \"apps.json\"", + "domain_cert_gen_failed": "Ne povis generi atestilon", + "regenconf_file_kept_back": "La agorda dosiero '{conf}' estas atendita forigi per regen-conf (kategorio {category}), sed ĝi estis konservita.", + "migrate_tsig_wait_4": "30 sekundoj …", + "backup_with_no_restore_script_for_app": "La apliko \"{app:s}\" ne havas restarigan skripton, vi ne povos aŭtomate restarigi la sekurkopion de ĉi tiu apliko.", + "log_letsencrypt_cert_install": "Instalu atestilon Ni ĉifru sur '{}' regado", + "log_dyndns_update": "Ĝisdatigu la IP asociita kun via subdominio YunoHost '{}'", + "firewall_reload_failed": "Ne eblis reŝargi la firewall", + "confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers: s}] ", + "log_user_delete": "Forigi uzanton '{}'", + "dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS", + "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", + "migration_0003_patching_sources_list": "Patching the sources.lists …", + "global_settings_setting_security_ssh_compatibility": "Kongruo vs sekureca kompromiso por la SSH-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", + "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{malavantaĝo}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", + "regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'", + "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path:s}'", + "migration_0006_disclaimer": "YunoHost nun atendas ke pasvortoj kaj administrantoj estu sinkronigitaj. Per ekzekuto de ĉi tiu migrado, via radika pasvorto estos anstataŭigita per administra pasvorto.", + "dyndns_ip_update_failed": "Ne povis ĝisdatigi IP-adreson al DynDNS", + "migration_description_0004_php5_to_php7_pools": "Rekonfigu la PHP-naĝejojn por uzi PHP 7 anstataŭ 5", + "monitor_glances_con_failed": "Ne povis konektiĝi al servilo de Glances", + "ssowat_conf_updated": "SSOwat-agordo ĝisdatigita", + "log_link_to_failed_log": "Ne povis plenumi la operacion '{desc}'. Bonvolu provizi la plenan protokolon de ĉi tiu operacio per alklakante ĉi tie por akiri helpon", + "log_app_fetchlist": "Aldonu liston de aplikoj", + "user_home_creation_failed": "Ne povis krei dosierujon \"home\" por uzanto", + "pattern_backup_archive_name": "Devas esti valida dosiernomo kun maksimume 30 signoj, alfanombraj kaj -_. signoj nur", + "restore_cleaning_failed": "Ne eblis purigi la adresaron de provizora restarigo", + "dyndns_registration_failed": "Ne povis registri DynDNS-domajnon: {error:s}", + "migration_0003_not_jessie": "La nuna Debian-distribuo ne estas Jessie!", + "user_unknown": "Nekonata uzanto: {user:s}", + "migrations_to_be_ran_manually": "Migrado {id} devas funkcii permane. Bonvolu iri al Iloj → Migradoj en la retpaĝa paĝo, aŭ kuri `yunohost tools migrations migrate`.", + "migration_0008_warning": "Se vi komprenas tiujn avertojn kaj konsentas lasi YunoHost pretervidi vian nunan agordon, faru la migradon. Alie, vi ankaŭ povas salti la migradon - kvankam ĝi ne rekomendas.", + "certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain:s}'", + "global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path:s}", + "pattern_domain": "Devas esti valida domajna nomo (t.e. mia-domino.org)", + "package_unexpected_error": "Neatendita eraro okazis prilaborante la pakon '{pkgname}'", + "dyndns_key_generating": "Generi DNS-ŝlosilon ... Eble daŭros iom da tempo.", + "restore_running_app_script": "Restarigi la programon '{app:s}'…", + "migrations_skip_migration": "Salti migradon {id}…", + "mysql_db_init_failed": "MysQL-datumbazo init malsukcesis", + "regenconf_file_removed": "Agordodosiero '{conf}' forigita", + "log_tools_shutdown": "Enŝaltu vian servilon", + "password_too_simple_3": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklon, pli malaltan kaj specialajn signojn", + "migration_0003_general_warning": "Bonvolu noti, ke ĉi tiu migrado estas delikata operacio. La teamo de YunoHost faris sian plej bonan revizii kaj testi ĝin, sed la migrado eble ankoraŭ rompos partojn de la sistemo aŭ ĝiaj programoj.\n\nTial oni rekomendas al:\n - Elfari kopion de iuj kritikaj datumoj aŭ app. Pliaj informoj pri https://yunohost.org/backup;\n - Paciencu post lanĉo de la migrado: Depende de via interreta konekto kaj aparataro, eble daŭros kelkaj horoj ĝis ĉio ĝisdatigi.\n\nAldone, la haveno por SMTP, uzata de eksteraj retpoŝtaj klientoj (kiel Thunderbird aŭ K9-Mail) estis ŝanĝita de 465 (SSL / TLS) al 587 (STARTTLS). La malnova haveno (465) aŭtomate fermiĝos, kaj la nova haveno (587) malfermiĝos en la fajrejo. Vi kaj viaj uzantoj * devos adapti la agordon de viaj retpoŝtaj klientoj laŭe.", + "diagnosis_kernel_version_error": "Ne povis akiri la kernan version: {error}", + "global_settings_setting_example_int": "Ekzemple int elekto", + "backup_output_symlink_dir_broken": "Vi havas rompitan simbolon anstataŭ via arkiva dosierujo '{path:s}'. Vi eble havas specifan agordon por sekurkopi viajn datumojn en alia dosiersistemo, ĉi-kaze vi probable forgesis remeti aŭ enŝovi vian malmolan disko aŭ ŝlosilon USB.", + "good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).", + "certmanager_attempt_to_renew_valid_cert": "La atestilo por la domajno '{domain:s}' ne finiĝos! (Vi eble uzos --force se vi scias kion vi faras)", + "restore_running_hooks": "Kurantaj restarigaj hokoj…", + "regenconf_pending_applying": "Aplikante pritraktata agordo por kategorio '{category}'…", + "service_description_dovecot": "Permesas al retpoŝtaj klientoj aliri / serĉi retpoŝton (per IMAP kaj POP3)", + "domain_dns_conf_is_just_a_recommendation": "Ĉi tiu komando montras al vi la *rekomenditan* agordon. Ĝi efektive ne agordas la DNS-agordon por vi. Via respondeco agordi vian DNS-zonon en via registristo laŭ ĉi tiu rekomendo.", + "backup_php5_to_php7_migration_may_fail": "Ne povis konverti vian ar archiveivon por subteni PHP 7, vi eble ne povas restarigi viajn PHP-programojn (kialo: {error:s})", + "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", + "log_app_change_url": "Ŝanĝu la URL de apliko '{}'", + "service_already_started": "La servo '{service:s}' estas jam komencita", + "license_undefined": "nedifinita", + "global_settings_setting_security_password_admin_strength": "Admin pasvorta forto", + "service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "migrations_list_conflict_pending_done": "Vi ne povas uzi ambaŭ '--previous' kaj '--done' samtempe.", + "maindomain_changed": "La ĉefa domajno nun ŝanĝiĝis", + "server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers:s}]", + "monitor_period_invalid": "Nevalida tempoperiodo", + "log_backup_restore_app": "Restarigu '{}' de rezerva ar archiveivo", + "log_does_exists": "Ne estas operacio-registro kun la nomo '{log}', uzu 'yunohost loglist' por vidi ĉiujn disponeblajn operaciojn", + "service_add_failed": "Ne povis aldoni la servon '{service:s}'", + "pattern_password_app": "Bedaŭrinde, pasvortoj ne povas enhavi jenajn signojn: {forbidden_chars}", + "this_action_broke_dpkg": "Ĉi tiu ago rompis dpkg / APT (la administrantoj pri la paka sistemo) ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.", + "log_regen_conf": "Regeneri sistemajn agordojn '{}'", + "restore_hook_unavailable": "La restariga skripto por '{part:s}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo", + "network_check_smtp_ok": "Eksteren retpoŝto (SMTP-haveno 25) ne estas blokita", + "log_dyndns_subscribe": "Aboni al YunoHost-subdominio '{}'", + "password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn", + "migration_0003_main_upgrade": "Komencanta ĉefa ĝisdatigo …", + "user_info_failed": "Ne povis akiri informojn pri uzanto", + "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", + "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log display {name}'", + "global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)", + "no_internet_connection": "Servilo ne konektita al la interreto", + "migration_0008_dsa": "• La DSA-ŝlosilo estos malŝaltita. Tial vi eble bezonos nuligi spuran averton de via SSH-kliento kaj revizii la fingrospuron de via servilo;", + "migration_0003_restoring_origin_nginx_conf": "Fileia dosiero /etc/nginx/nginx.conf estis iel redaktita. La migrado reaperos unue al sia originala stato ... La antaŭa dosiero estos havebla kiel {backup_dest}.", + "migrate_tsig_end": "Migrado al HMAC-SHA-512 finiĝis", + "restore_complete": "Restarigita", + "certmanager_couldnt_fetch_intermediate_cert": "Ekvilibrigita kiam vi provis ricevi interajn atestilojn de Let's Encrypt. Atestita instalado / renovigo nuligita - bonvolu reprovi poste.", + "hook_exec_failed": "Ne povis funkcii skripto: {path:s}", + "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason:s}", + "user_created": "Uzanto kreita", + "service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto", + "certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domajno:s}! (Uzu --forte pretervidi)", + "monitor_stats_period_unavailable": "Ne ekzistas disponeblaj statistikoj por la periodo", + "regenconf_updated": "Agordo por kategorio '{category}' ĝisdatigita", + "update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}", + "regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…", + "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", + "global_settings_setting_example_string": "Ekzemple korda elekto", + "restore_already_installed_app": "App kun la ID '{app:s}' estas jam instalita", + "mountpoint_unknown": "Nekonata montpunkto", + "log_tools_maindomain": "Faru de '{}' la ĉefa domajno", + "maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon", + "mail_domain_unknown": "Nekonata retpoŝtadreso por domajno '{domain:s}'", + "migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo% s", + "pattern_email": "Devas esti valida retpoŝtadreso (t.e.iu@domain.org)", + "mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'", + "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", + "monitor_enabled": "Servila monitorado nun ŝaltis", + "domain_exists": "La domajno jam ekzistas", + "migration_description_0001_change_cert_group_to_sslcert": "Ŝanĝu grupajn permesojn de 'metronomo' al 'ssl-cert'", + "mysql_db_creation_failed": "MySQL-datumbazkreado malsukcesis", + "ldap_initialized": "LDAP inicializis", + "migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.", + "certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)", + "certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file: s})", + "log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno", + "log_tools_reboot": "Reklamu vian servilon", + "certmanager_cert_install_success": "Ni Ĉifru atestilon nun instalitan por la domajno '{domain:s}'", + "global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting:s}, ricevita '{choice:s}', sed disponeblaj elektoj estas: {available_choices:s}", + "server_shutdown": "La servilo haltos", + "log_tools_migrations_migrate_forward": "Migri antaŭen", + "migration_0008_no_warning": "Neniu grava risko identigita pri superregado de via SSH-agordo, tamen oni ne povas esti absolute certa;)! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon - kvankam ĝi ne rekomendas.", + "regenconf_now_managed_by_yunohost": "La agorda dosiero '{conf}' nun estas administrata de YunoHost (kategorio {category}).", + "server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers:s}]", + "log_app_install": "Instalu la aplikon '{}'", + "service_description_dnsmasq": "Traktas rezolucion de domajna nomo (DNS)", + "global_settings_unknown_type": "Neatendita situacio, la agordo {setting:s} ŝajnas havi la tipon {unknown_type:s} sed ĝi ne estas tipo subtenata de la sistemo.", + "migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj apps estis detektitaj. Ĝi aspektas, ke tiuj ne estis instalitaj de aparato aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}", + "domain_hostname_failed": "Ne povis agordi novan gastigilon. Ĉi tio eble kaŭzos problemon poste (eble bone).", + "server_reboot": "La servilo rekomenciĝos", + "regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}", + "domain_uninstall_app_first": "Unu aŭ pluraj programoj estas instalitaj en ĉi tiu domajno. Bonvolu malinstali ilin antaŭ ol daŭrigi la domajnan forigon", + "port_unavailable": "Haveno {port:d} ne haveblas", + "service_unknown": "Nekonata servo '{service:s}'", + "migration_0003_start": "Komencante migradon al Stretch. La protokoloj haveblos en {logfile}.", + "monitor_stats_no_update": "Neniuj monitoradaj statistikoj ĝisdatigi", + "domain_deletion_failed": "Ne povis forigi domajnon {domain}: {error}", + "log_user_update": "Ĝisdatigu uzantinformojn de '{}'", + "user_creation_failed": "Ne povis krei uzanton {user}: {error}", + "migrations_migration_has_failed": "Migrado {id} ne kompletigis, abolis. Eraro: {exception}", + "done": "Farita", + "log_domain_remove": "Forigi domon '{}' de agordo de sistemo", + "monitor_not_enabled": "Servila monitorado estas malŝaltita", + "diagnosis_debian_version_error": "Ne povis retrovi la Debianan version: {error}", + "hook_list_by_invalid": "Ĉi tiu posedaĵo ne povas esti uzata por listigi hokojn", + "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers: s}'", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permesu uzon de (malaktuala) DSA-hostkey por la agordo de daemon SSH", + "dyndns_domain_not_provided": "Provizanto DynDNS {provider:s} ne povas provizi domajnon {domain:s}.", + "backup_unable_to_organize_files": "Ne povis uzi la rapidan metodon por organizi dosierojn en la ar archiveivo", + "password_too_simple_2": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklojn kaj minusklojn", + "executing_script": "Plenumanta skripto '{script:s}' …", + "service_cmd_exec_failed": "Ne povis plenumi la komandon '{command:s}'", + "migration_0007_cancelled": "YunoHost ne plibonigis la administradon de via SSH-konf.", + "migrate_tsig_failed": "Ne povis migri la DynDNS-domajnon '{domain}' al HMAC-SHA-512, ruliĝante. Eraro: {error_code}, {error}", + "pattern_lastname": "Devas esti valida familinomo", + "service_enabled": "'{service:s}' servo malŝaltita", + "certmanager_no_cert_file": "Ne povis legi la atestan dosieron por la domajno {domain:s} (dosiero: {file:s})", + "migration_0008_port": "• Vi devos konekti uzante la havenon 22 anstataŭ via nuna kutimo SSH-haveno. Sentu vin libera reconfiguri ĝin;", + "domain_creation_failed": "Ne povis krei domajnon {domain}: {error}", + "certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas", + "domain_cannot_remove_main": "Ne eblas forigi ĉefan domajnon. Fiksu unu unue", + "service_reloaded_or_restarted": "'{service:s}' servo reŝarĝis aŭ rekomencis", + "mysql_db_initialized": "La datumbazo MySQL jam estas pravalorizita", + "log_domain_add": "Aldonu '{}' domajnon en sisteman agordon", + "global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting:s}, ricevita {received_type:s}, atendata {expected_type:s}", + "unlimit": "Neniu kvoto", + "dyndns_cron_installed": "Kreita laboro DynDNS cron", + "system_username_exists": "Uzantnomo jam ekzistas en la listo de uzantoj de sistemo", + "firewall_reloaded": "Fajroŝirmilo reŝarĝis", + "service_restarted": "'{service:s}' servo rekomencis", + "pattern_username": "Devas esti minuskulaj literoj kaj minuskloj nur", + "extracting": "Eltirante…", + "restore_app_failed": "Ne povis restarigi la programon '{app:s}'", + "yunohost_configured": "YunoHost nun agordis", + "certmanager_self_ca_conf_file_not_found": "Ne povis trovi agorddosieron por mem-subskriba aŭtoritato (dosiero: {file:s})", + "log_app_remove": "Forigu la aplikon '{}'", + "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", + "firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.", + "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …" } From 8cdea18991b60e4489d93d2b861434ca3103bd53 Mon Sep 17 00:00:00 2001 From: advocatux Date: Fri, 4 Oct 2019 18:14:15 +0000 Subject: [PATCH 87/92] Translated using Weblate (Spanish) Currently translated at 100.0% (553 of 553 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 59 +++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/locales/es.json b/locales/es.json index 02f46652b..aeee0ff7a 100644 --- a/locales/es.json +++ b/locales/es.json @@ -29,7 +29,7 @@ "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", "app_upgrade_failed": "No se pudo actualizar {app:s}", "app_upgraded": "Actualizado {app:s}", - "appslist_fetched": "Obtenida lista de aplicaciones {appslist:s} actualizada", + "appslist_fetched": "Lista de aplicaciones {appslist:s} actualizada", "appslist_removed": "Eliminada la lista de aplicaciones {appslist:s}", "appslist_retrieve_error": "No se puede recuperar la lista remota de aplicaciones {appslist:s}: {error:s}", "appslist_unknown": "Lista de aplicaciones {appslist:s} desconocida.", @@ -74,9 +74,9 @@ "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute «apt-get remove bind9 && apt-get install it»", "domain_cert_gen_failed": "No se pudo generar el certificado", "domain_created": "Dominio creado", - "domain_creation_failed": "No se pudo crear el dominio", + "domain_creation_failed": "No se pudo crear el dominio {domain}: {error}", "domain_deleted": "Dominio eliminado", - "domain_deletion_failed": "No se pudo eliminar el dominio", + "domain_deletion_failed": "No se pudo eliminar el dominio {domain}: {error}", "domain_dyndns_already_subscribed": "Ya se ha suscrito a un dominio de DynDNS", "domain_dyndns_invalid": "Este dominio no se puede usar con DynDNS", "domain_dyndns_root_unknown": "Dominio raíz de DynDNS desconocido", @@ -228,13 +228,13 @@ "upnp_enabled": "UPnP activado", "upnp_port_open_failed": "No se pudo abrir el puerto vía UPnP", "user_created": "Usuario creado", - "user_creation_failed": "No se pudo crear el usuario", + "user_creation_failed": "No se pudo crear el usuario {user}: {error}", "user_deleted": "Usuario eliminado", - "user_deletion_failed": "No se pudo eliminar el usuario", + "user_deletion_failed": "No se pudo eliminar el usuario {user}: {error}", "user_home_creation_failed": "No se pudo crear la carpeta «home» para el usuario", "user_info_failed": "No se pudo obtener la información del usuario", "user_unknown": "Usuario desconocido: {user:s}", - "user_update_failed": "No se pudo cambiar la información del usuario", + "user_update_failed": "No se pudo actualizar el usuario {user}: {error}", "user_updated": "Cambiada la información de usuario", "yunohost_already_installed": "YunoHost ya está instalado", "yunohost_ca_creation_failed": "No se pudo crear la autoridad de certificación", @@ -398,16 +398,16 @@ "remove_main_permission_not_allowed": "No se permite eliminar el permiso principal", "recommend_to_add_first_user": "La posinstalación ha terminado pero YunoHost necesita al menos un usuario para funcionar correctamente, debe añadir uno ejecutando «yunohost user create » o usando la interfaz de administración.", "permission_update_nothing_to_do": "No hay permisos para actualizar", - "permission_updated": "Actualizado el permiso «{permission:s}» para la aplicación «{app:s}»", + "permission_updated": "Actualizado el permiso «{permission:s}»", "permission_generated": "Actualizada la base de datos de permisos", - "permission_update_failed": "No se pudo actualizar el permiso", + "permission_update_failed": "No se pudo actualizar el permiso «{permission}» : {error}", "permission_name_not_valid": "Elija un nombre de permiso permitido para «{permission:s}", - "permission_not_found": "No se encontró el permiso «{permission:s}» para la aplicación «{app:s}»", - "permission_deletion_failed": "Falta el permiso «{permission:s}» para eliminar la aplicación «{app:s}»", - "permission_deleted": "Eliminado el permiso «{permission:s}» para la aplicación {app:s}", - "permission_creation_failed": "No se pudo conceder el permiso", - "permission_created": "Creado el permiso «{permission:s}» para la aplicación {app:s}", - "permission_already_exist": "El permiso «{permission:s}» para la aplicación {app:s} ya existe", + "permission_not_found": "No se encontró el permiso «{permission:s}»", + "permission_deletion_failed": "No se pudo eliminar el permiso «{permission}»: {error}", + "permission_deleted": "Eliminado el permiso «{permission:s}»", + "permission_creation_failed": "No se pudo crear el permiso «{permission}»: {error}", + "permission_created": "Creado el permiso «{permission:s}»", + "permission_already_exist": "El permiso «{permission}» ya existe", "permission_already_clear": "El permiso «{permission:s}» ya está definido para la aplicación {app:s}", "pattern_password_app": "Las contraseñas no pueden incluir los siguientes caracteres: {forbidden_chars}", "need_define_permission_before": "Redefina los permisos ejecutando «yunohost user permission add -u USUARIO» antes de eliminar un grupo permitido", @@ -438,7 +438,7 @@ "migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, reiniciar la configuración original ejecutando «yunohost tools regen-conf -f» y reintentar la migración", "migration_0011_done": "Migración correcta. Ahora puede gestionar los grupos de usuarios.", "migration_0011_create_group": "Creando un grupo para cada usuario…", - "migration_0011_can_not_backup_before_migration": "No se pudo respaldar el sistema antes de la migración. Error: {error:s}", + "migration_0011_can_not_backup_before_migration": "Falló el respaldo del sistema antes de la migración. Fallo de migración. Error: {error:s}", "migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.", "migration_0009_not_needed": "La migración ya ocurrió de algún modo… (?) Omitiendo.", "migration_0008_no_warning": "No se ha detectado ningún riesgo importante con respecto a la anulación de su configuración SSH ¡sin embargo uno nunca puede estar absolutamente seguro ;)! Ejecute la migración para anularla. Por otra parte, puede omitir la migración aunque no esté recomendado.", @@ -536,14 +536,14 @@ "log_category_404": "La categoría de registro «{category}» no existe", "log_corrupted_md_file": "El archivo de metadatos YAML asociado con el registro está dañado: «{md_file}\nError: {error}»", "hook_json_return_error": "No se pudo leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}", - "group_update_failed": "No se pudo actualizar el grupo «{group}»", + "group_update_failed": "No se pudo actualizar el grupo «{group}»: {error}", "group_updated": "Grupo «{group}» actualizado", "group_unknown": "El grupo «{group:s}» es desconocido", "group_info_failed": "No se pudo mostrar la información del grupo", "group_deletion_not_allowed": "No se puede eliminar el grupo {group:s} manualmente.", - "group_deletion_failed": "No se pudo eliminar el grupo «{group}»", + "group_deletion_failed": "No se pudo eliminar el grupo «{group}»: {error}", "group_deleted": "Eliminado el grupo «{group}»", - "group_creation_failed": "No se pudo crear el grupo «{group}»", + "group_creation_failed": "No se pudo crear el grupo «{group}»: {error}", "group_created": "Creado el grupo «{group}»", "group_name_already_exist": "El grupo {name:s} ya existe", "group_already_disallowed": "El grupo «{group:s}» ya tiene desactivado el permiso «{permission:s}» para la aplicación «{app:s}»", @@ -577,8 +577,8 @@ "domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.", "dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento porque otro programa parece que está usando el bloqueo de dpkg (el gestor de paquetes del sistema)", "dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.", - "confirm_app_install_thirdparty": "¡AVISO! Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarlas salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ", - "confirm_app_install_danger": "¡AVISO! Esta aplicación es aún experimental (si no está funcionando expresamente) y ¡es probable que rompa su sistema! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ", + "confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema... Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", + "confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación es conocida por ser aún experimental (o no funciona explícitamente)! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema... Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»", "confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ", "backup_unable_to_organize_files": "No se pudo usar el método rápido de organización de los archivos en el archivo", "backup_permission": "Permiso de respaldo para la aplicación {app:s}", @@ -608,5 +608,22 @@ "app_action_broke_system": "Esta acción parece que ha roto estos importantes servicios: {services}", "operation_interrupted": "¿Ha sido interrumpida la operación manualmente?", "apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas", - "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído." + "dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído.", + "group_already_exist": "El grupo {group} ya existe", + "group_already_exist_on_system": "El grupo {group} ya existe en los grupos del sistema", + "group_cannot_be_edited": "El grupo {group} no se puede editar manualmente.", + "group_cannot_be_deleted": "El grupo {group} no se puede eliminar manualmente.", + "group_user_already_in_group": "El usuario {user} ya está en el grupo {group}", + "group_user_not_in_group": "El usuario {user} no está en el grupo {group}", + "log_permission_create": "Crear permiso «{}»", + "log_permission_delete": "Eliminar permiso «{}»", + "log_permission_urls": "Actualizar URLs relacionadas con el permiso «{}»", + "log_user_group_create": "Crear grupo «{}»", + "log_user_permission_update": "Actualizar los accesos para el permiso «{}»", + "log_user_permission_reset": "Restablecer permiso «{}»", + "migration_0011_failed_to_remove_stale_object": "No se pudo eliminar el objeto obsoleto {dn}: {error}", + "permission_already_allowed": "El grupo «{group}» ya tiene el permiso «{permission}» activado", + "permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado", + "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", + "user_already_exists": "El usuario {user} ya existe" } From 3e6ae5fb6835246141575c140897e7fd7a4db6c9 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Sun, 6 Oct 2019 10:07:41 +0000 Subject: [PATCH 88/92] Translated using Weblate (French) Currently translated at 100.0% (553 of 553 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 61 +++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 8bffec8b2..6b5e2a790 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -30,7 +30,7 @@ "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s}", "app_upgraded": "{app:s} mis à jour", - "appslist_fetched": "La liste d’applications {appslist:s} récupérée", + "appslist_fetched": "La liste d’applications mise à jour {appslist:s}", "appslist_removed": "La liste d’applications {appslist:s} a été supprimée", "appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante {appslist:s} : {error:s}", "appslist_unknown": "La liste d’applications {appslist:s} est inconnue.", @@ -75,9 +75,9 @@ "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cert_gen_failed": "Impossible de générer le certificat", "domain_created": "Le domaine a été créé", - "domain_creation_failed": "Impossible de créer le domaine", + "domain_creation_failed": "Impossible de créer le domaine {domain}: {error}", "domain_deleted": "Le domaine a été supprimé", - "domain_deletion_failed": "Impossible de supprimer le domaine", + "domain_deletion_failed": "Impossible de supprimer le domaine {domain}: {error}", "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", @@ -236,13 +236,13 @@ "upnp_enabled": "UPnP activé", "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", "user_created": "L’utilisateur créé", - "user_creation_failed": "Impossible de créer l’utilisateur", + "user_creation_failed": "Impossible de créer l’utilisateur {user}: {error}", "user_deleted": "L’utilisateur supprimé", - "user_deletion_failed": "Impossible de supprimer l’utilisateur", + "user_deletion_failed": "Impossible de supprimer l’utilisateur {user}: {error}", "user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur", "user_info_failed": "Impossible de récupérer les informations de l’utilisateur", "user_unknown": "L'utilisateur {user:s} est inconnu", - "user_update_failed": "Impossible de modifier l’utilisateur", + "user_update_failed": "Impossible de mettre à jour l'utilisateur {utilisateur}: {erreur}", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", @@ -493,8 +493,8 @@ "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …", "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …", "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{answers:s}] ", - "confirm_app_install_danger": "AVERTISSEMENT ! Cette application est encore expérimentale (explicitement, elle ne fonctionne pas) et risque de casser votre système ! Vous ne devriez probablement PAS l'installer sans savoir ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{answers:s}] ", - "confirm_app_install_thirdparty": "AVERTISSEMENT ! L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer si vous ne savez pas ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{answers:s}] ", + "confirm_app_install_danger": "DANGER! Cette application est connue pour être encore expérimentale (si elle ne fonctionne pas explicitement)! Vous ne devriez probablement PAS l'installer à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système ... Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers: s}'", + "confirm_app_install_thirdparty": "DANGER! Cette application ne fait pas partie du catalogue d'applications de Yunohost. L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système ... Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers:s}'", "dpkg_is_broken": "Vous ne pouvez pas faire ça maintenant car dpkg/apt (le gestionnaire de paquets du système) semble avoir laissé des choses non configurées. Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a'.", "dyndns_could_not_check_available": "Impossible de vérifier si {domain:s} est disponible chez {provider:s}.", "file_does_not_exist": "Le fichier dont le chemin est {path:s} n'existe pas.", @@ -573,12 +573,12 @@ "group_info_failed": "L'information sur le groupe a échoué", "group_unknown": "Le groupe {group:s} est inconnu", "group_updated": "Le groupe '{group}' a été mis à jour", - "group_update_failed": "La mise à jour du groupe '{group}' a échoué", + "group_update_failed": "La mise à jour du groupe '{group}' a échoué : {error}", "group_already_allowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' activée pour l'application '{app:s}'", "group_already_disallowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' désactivée pour l'application '{app:s}'", "group_name_already_exist": "Le groupe {name:s} existe déjà", - "group_creation_failed": "Échec de la création du groupe '{group}'", - "group_deletion_failed": "Échec de la suppression du groupe '{group}'", + "group_creation_failed": "Échec de la création du groupe '{group}': {error}", + "group_deletion_failed": "Échec de la suppression du groupe '{group}': {error}", "edit_permission_with_group_all_users_not_allowed": "Vous n'êtes pas autorisé à modifier les permissions pour le groupe 'all_users', utilisez 'yunohost user permission clear APP' ou 'yunohost user permission add APP -u USER' à la place.", "log_permission_add": "Ajouter l'autorisation '{}' pour l'application '{}'", "log_permission_remove": "Supprimer l'autorisation '{}'", @@ -600,7 +600,7 @@ "migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales", "migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.", "migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}", - "migration_0011_can_not_backup_before_migration": "Impossible de sauvegarder le système avant la migration. Erreur: {error: s}", + "migration_0011_can_not_backup_before_migration": "La sauvegarde du système avant la migration a échoué. La migration a échoué. Erreur: {error: s}", "migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…", "migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… essayait de restauration du système.", "migration_0011_rollback_success": "Système restauré.", @@ -610,11 +610,11 @@ "user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group: s}'", "user_not_in_group": "L'utilisateur '{user: s}' ne fait pas partie du groupe {group: s}", "migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.", - "permission_not_found": "Autorisation '{permission: s}' non trouvée pour l'application '{app: s}'", + "permission_not_found": "Autorisation '{permission:s}' introuvable", "permission_name_not_valid": "Choisissez un nom d'autorisation autorisé pour '{permission: s}'", - "permission_update_failed": "Impossible de mettre à jour la permission", + "permission_update_failed": "Impossible de mettre à jour la permission '{permission}': {error}", "permission_generated": "Base de données des autorisations mise à jour", - "permission_updated": "Permission '{permission: s}' pour l'application '{app: s}' mise à jour", + "permission_updated": "Permission '{permission:s}' mise à jour", "permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour", "remove_main_permission_not_allowed": "Supprimer l'autorisation principale n'est pas autorisé", "dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur Dyndns {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.", @@ -627,13 +627,30 @@ "need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé", "operation_interrupted": "L'opération a été interrompue manuellement", "permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}", - "permission_already_exist": "L'autorisation '{permission: s}' pour l'application {app: s} existe déjà", - "permission_created": "Permission '{permission: s}' pour l'application {app: s} créée", - "permission_creation_failed": "Impossible d'accorder la permission", - "permission_deleted": "Permission '{permission: s}' pour app {app: s} supprimée", - "permission_deletion_failed": "Autorisation manquante '{permission: s}' pour supprimer l'application '{app: s}'", + "permission_already_exist": "L'autorisation '{permission}' existe déjà", + "permission_created": "Permission '{permission:s}' créée", + "permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {erreur}", + "permission_deleted": "Permission '{permission:s}' supprimée", + "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", "migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services", - "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", - "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}" + "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration.", + "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}", + "group_already_exist": "Le groupe {group} existe déjà", + "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", + "group_cannot_be_edited": "Le groupe {group} ne peut pas être édité manuellement.", + "group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.", + "group_user_already_in_group": "L'utilisateur {user} est déjà dans le groupe {group}", + "group_user_not_in_group": "L'utilisateur {user} n'est pas dans le groupe {group}", + "log_permission_create": "Créer permission '{}'", + "log_permission_delete": "supprimer permission '{}'", + "log_permission_urls": "Mettre à jour les URL liées à la permission '{}'", + "log_user_group_create": "Créer '{}' groupe", + "log_user_permission_update": "Mise à jour des accès pour la permission '{}'", + "log_user_permission_reset": "Réinitialiser la permission '{}'", + "migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet obsolète {dn}: {error}", + "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée '", + "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", + "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", + "user_already_exists": "L'utilisateur {user} existe déjà" } From d8468f77bc0d8554fcc17e61d228d3466007ee5c Mon Sep 17 00:00:00 2001 From: advocatux Date: Tue, 8 Oct 2019 17:07:51 +0000 Subject: [PATCH 89/92] Translated using Weblate (Spanish) Currently translated at 100.0% (554 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index aeee0ff7a..80b17673a 100644 --- a/locales/es.json +++ b/locales/es.json @@ -625,5 +625,6 @@ "permission_already_allowed": "El grupo «{group}» ya tiene el permiso «{permission}» activado", "permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado", "permission_cannot_remove_main": "No está permitido eliminar un permiso principal", - "user_already_exists": "El usuario {user} ya existe" + "user_already_exists": "El usuario {user} ya existe", + "app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación." } From 5e477dd3f2d8df0ad74dcc9d7e7c96a8e1bb830f Mon Sep 17 00:00:00 2001 From: Lukas Dohn Date: Wed, 9 Oct 2019 08:31:31 +0000 Subject: [PATCH 90/92] Translated using Weblate (German) Currently translated at 39.2% (217 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index d03226187..1fe279d6b 100644 --- a/locales/de.json +++ b/locales/de.json @@ -282,7 +282,7 @@ "appslist_name_already_tracked": "Es gibt bereits eine registrierte App-Liste mit Namen {name:s}.", "appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit der URL {url:s}.", "appslist_migrating": "Migriere Anwendungsliste {appslist:s} …", - "appslist_could_not_migrate": "Konnte Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.", + "appslist_could_not_migrate": "Konnte die Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.", "appslist_corrupted_json": "Anwendungslisten konnte nicht geladen werden. Es scheint, dass {filename:s} beschädigt ist.", "yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.", "app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.", From a6a104677cb0ef723645734bd4b67c0c95dbc66a Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 9 Oct 2019 11:20:08 +0000 Subject: [PATCH 91/92] Translated using Weblate (French) Currently translated at 100.0% (554 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 6b5e2a790..62f6d6965 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -634,7 +634,7 @@ "permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}", "remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'", "migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services", - "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration.", + "migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration", "migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}", "group_already_exist": "Le groupe {group} existe déjà", "group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système", @@ -652,5 +652,6 @@ "permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée '", "permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '", "permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé", - "user_already_exists": "L'utilisateur {user} existe déjà" + "user_already_exists": "L'utilisateur {user} existe déjà", + "app_full_domain_unavailable": "Désolé, cette application nécessite l'installation d'un domaine complet, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Une solution possible consiste à ajouter et à utiliser un sous-domaine dédié à cette application." } From 5ac863c906f6858f3433fbc2082fc3f544faee1a Mon Sep 17 00:00:00 2001 From: amirale qt Date: Wed, 9 Oct 2019 11:21:54 +0000 Subject: [PATCH 92/92] Translated using Weblate (Esperanto) Currently translated at 100.0% (554 of 554 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index d27c0171c..0d8d13fe8 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -552,5 +552,6 @@ "log_app_remove": "Forigu la aplikon '{}'", "service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}", "firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.", - "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …" + "certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …", + "app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu apliko postulas plenan domajnon esti instalita, sed iuj aliaj programoj jam estas instalitaj sur '{domain}'. Unu ebla solvo estas aldoni kaj uzi subdomajnon dediĉitan al ĉi tiu aplikaĵo anstataŭe." }