From d975ed2689b28134442b83a2c3d135da17732bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 22:31:40 +0100 Subject: [PATCH 01/58] Update LDAP config --- data/hooks/conf_regen/06-slapd | 2 +- data/other/ldap_scheme.yml | 53 +++++++++++++++++++--------- data/templates/slapd/slapd.conf | 32 ++++++++++++++++- data/templates/slapd/yunohost.schema | 33 +++++++++++++++++ src/yunohost/tools.py | 6 ++++ 5 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 data/templates/slapd/yunohost.schema diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index d0a1fad63..9ba223e4c 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -60,7 +60,7 @@ do_pre_regen() { # copy configuration files cp -a ldap.conf slapd.conf "$ldap_dir" - cp -a sudo.schema mailserver.schema "$schema_dir" + cp -a sudo.schema mailserver.schema yunohost.schema "$schema_dir" install -D -m 644 slapd.default "${pending_dir}/etc/default/slapd" } diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index 75bdea6e2..d30c4915c 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -17,6 +17,12 @@ parents: - organizationalUnit - top + ou=permission: + ou: permission + objectClass: + - organizationalUnit + - top + ou=groups: ou: groups objectClass: @@ -29,22 +35,6 @@ parents: - top children: - cn=admins,ou=groups: - cn: admins - gidNumber: "4001" - memberUid: admin - objectClass: - - posixGroup - - top - - cn=sftpusers,ou=groups: - cn: sftpusers - gidNumber: "4002" - memberUid: admin - objectClass: - - posixGroup - - top - cn=admin,ou=sudo: cn: admin sudoUser: admin @@ -54,3 +44,34 @@ children: objectClass: - sudoRole - top + cn=admins,ou=groups: + cn: admins + gidNumber: "4001" + memberUid: admin + objectClass: + - posixGroup + - top + cn=ALL,ou=groups: + cn: ALL + gidNumber: "4002" + objectClass: + - posixGroup + - groupOfNamesYnh + +depends_children: + cn=main.mail,ou=permission: + cn: main.mail + gidNumber: "5001" + objectClass: + - posixGroup + - permissionYnh + groupPermission: + - "cn=ALL,ou=groups,dc=yunohost,dc=org" + cn=main.metronome,ou=permission: + cn: main.metronome + gidNumber: "5002" + objectClass: + - posixGroup + - permissionYnh + groupPermission: + - "cn=ALL,ou=groups,dc=yunohost,dc=org" diff --git a/data/templates/slapd/slapd.conf b/data/templates/slapd/slapd.conf index 9a8800d9d..4acebe97e 100644 --- a/data/templates/slapd/slapd.conf +++ b/data/templates/slapd/slapd.conf @@ -14,6 +14,7 @@ include /etc/ldap/schema/nis.schema include /etc/ldap/schema/inetorgperson.schema include /etc/ldap/schema/mailserver.schema include /etc/ldap/schema/sudo.schema +include /etc/ldap/schema/yunohost.schema # Where the pid file is put. The init.d script # will not stop the server if you change this. @@ -31,7 +32,7 @@ password-hash {SSHA} # Where the dynamically loaded modules are stored modulepath /usr/lib/ldap moduleload back_mdb -moduleload memberof +moduleload memberof # The maximum number of entries that is returned for a search operation sizelimit 500 @@ -110,3 +111,32 @@ access to * by dn="cn=admin,dc=yunohost,dc=org" write by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write by * read + +# Configure Memberof Overlay (used for Yunohost permission) + +# Link user <-> group +#dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config +overlay memberof +memberof-group-oc groupOfNamesYnh +memberof-member-ad member +memberof-memberof-ad memberOf +memberof-dangling error +memberof-refint TRUE + +# Link permission <-> groupes +#dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config +overlay memberof +memberof-group-oc permissionYnh +memberof-member-ad groupPermission +memberof-memberof-ad permission +memberof-dangling error +memberof-refint TRUE + +# Link permission <-> user +#dn: olcOverlay={2}memberof,olcDatabase={1}mdb,cn=config +overlay memberof +memberof-group-oc permissionYnh +memberof-member-ad inheritPermission +memberof-memberof-ad permission +memberof-dangling error +memberof-refint TRUE diff --git a/data/templates/slapd/yunohost.schema b/data/templates/slapd/yunohost.schema new file mode 100644 index 000000000..7da60a20c --- /dev/null +++ b/data/templates/slapd/yunohost.schema @@ -0,0 +1,33 @@ +#dn: cn=yunohost,cn=schema,cn=config +#objectClass: olcSchemaConfig +#cn: yunohost +# ATTRIBUTES +# For Permission +attributetype ( 1.3.6.1.4.1.17953.9.1.1 NAME 'permission' + DESC 'Yunohost permission on user and group side' + SUP distinguishedName ) +attributetype ( 1.3.6.1.4.1.17953.9.1.2 NAME 'groupPermission' + DESC 'Yunohost permission for a group on permission side' + SUP distinguishedName ) +attributetype ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission' + DESC 'Yunohost permission for user on permission side' + SUP distinguishedName ) +attributetype ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL' + DESC 'Yunohost application URL' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) +# OBJECTCLASS +# For Applications +objectclass ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh' + DESC 'Yunohost user group' + SUP top AUXILIARY + MAY ( member $ businessCategory $ seeAlso $ owner $ ou $ o $ permission ) ) +objectclass ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh' + DESC 'a Yunohost application' + SUP top AUXILIARY + MUST cn + MAY ( groupPermission $ inheritPermission $ URL ) ) +# For User +objectclass ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh' + DESC 'a Yunohost application' + SUP top AUXILIARY + MAY ( permission ) ) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 189b1db09..d58951878 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -89,6 +89,12 @@ def tools_ldapinit(): except Exception as e: logger.warn("Error when trying to inject '%s' -> '%s' into ldap: %s" % (rdn, attr_dict, e)) + for rdn, attr_dict in ldap_map['depends_children'].items(): + try: + auth.add(rdn, attr_dict) + except Exception as e: + logger.warn("Error when trying to inject '%s' -> '%s' into ldap: %s" % (rdn, attr_dict, e)) + admin_dict = { 'cn': 'admin', 'uid': 'admin', From 7b8d6688466469dbca2b3a6aa4dde184a05a00d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 22:47:02 +0100 Subject: [PATCH 02/58] Update actionsmap for group and permissions --- data/actionsmap/yunohost.yml | 189 ++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 38e311546..afd2ed3b3 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -63,7 +63,7 @@ _global: # User # ############################# user: - category_help: Manage users + category_help: Manage users and groups actions: ### user_list() @@ -217,6 +217,193 @@ user: help: Username or email to get information subcategories: + group: + subcategory_help: Manage group + actions: + ### user_group_list() + list: + action_help: List group + api: GET /users/groups + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + --fields: + help: fields to fetch + nargs: "+" + + ### user_group_add() + add: + action_help: Create group + api: POST /users/groups + configuration: + authenticate: all + arguments: + groupname: + help: The unique group name to add + extra: + pattern: &pattern_groupname + - !!str ^[a-z0-9_]+$ + - "pattern_groupname" + + ### user_group_delete() + delete: + action_help: Delete group + api: DELETE /users/groups/ + configuration: + authenticate: all + arguments: + groupname: + help: Username to delete + extra: + pattern: *pattern_groupname + + ### user_group_update() + update: + action_help: Update group + api: PUT /users/groups/ + configuration: + authenticate: all + arguments: + groupname: + help: Username to update + extra: + pattern: *pattern_groupname + -a: + full: --add-user + help: User to add in group + nargs: "*" + metavar: USERNAME + extra: + pattern: *pattern_username + -r: + full: --remove-user + help: User to remove in group + nargs: "*" + metavar: USERNAME + extra: + pattern: *pattern_username + + ### user_group_info() + info: + action_help: Get group information + api: GET /users/ + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + groupname: + help: Groupname to get information + extra: + pattern: *pattern_username + + permission: + subcategory_help: Manage user permission + actions: + ### user_permission_list() + list: + action_help: List access to user and group + api: GET /users/permission/ + configuration: + authenticate: all + authenticator: ldap-anonymous + 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 + + ### user_permission_add() + add: + action_help: Grant access right to users and group + api: POST /users/permission/ + configuration: + authenticate: all + 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 + extra: + pattern: *pattern_username + + ### user_permission_remove() + remove: + action_help: Revoke access right to users and group + api: PUT /users/permission/ + configuration: + authenticate: all + 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 + extra: + pattern: *pattern_username + + ## user_permission_clear() + clear: + action_help: Reset access rights for the app + api: DELETE /users/permission/ + configuration: + authenticate: all + arguments: + app: + help: Application to manage the permission + nargs: "*" + -p: + full: --permission + help: Name of permission (main by default) + nargs: "*" + metavar: PERMISSION ssh: subcategory_help: Manage ssh access From bb892bb1a49a5561f8e370b80d4131e268384334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 22:50:48 +0100 Subject: [PATCH 03/58] Implement group management --- locales/en.json | 35 +++++ src/yunohost/user.py | 342 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 346 insertions(+), 31 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5ef9f5c0c..5b78cdcd7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -180,6 +180,7 @@ "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}", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", @@ -207,6 +208,18 @@ "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_alread_allowed": "Group '{group:s}' already allowed to access to permission '{permission:s}' for app '{app:s}'", + "group_alread_disallowed": "Group '{group:s}' already disallowed to access to permission '{permission:s}' for app '{app:s}'", + "group_name_already_exist": "Group {name:s} already exist", + "group_created": "Group creation success", + "group_creation_failed": "Group creation failed", + "group_deleted": "Group deleted", + "group_deletion_failed": "Group deletion failed", + "group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}", + "group_info_failed": "Group info failed", + "group_unknown": "Groupe {group:s} unknown", + "group_updated": "Groupe updated", + "group_update_failed": "groupe update failed", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", "hook_list_by_invalid": "Invalid property to list hook by", @@ -244,13 +257,20 @@ "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_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_service_enable": "Enable '{}' service", "log_service_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", + "log_user_group_add": "Add '{}' group", + "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_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_migrations_migrate_backward": "Migrate backward", @@ -370,11 +390,23 @@ "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", + "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", + "premission_creation_failled": "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_name_not_valid": "Permission name '{permission:s}' not valid", + "permission_update_failed": "Permission update failed", + "permission_updated": "Permission '{permission:s}' for app {app:s} updated", "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", + "remove_user_of_group_not_allowed": "You are not allowed to remove the user {user:s} in the group {group:s}", "restore_action_required": "You must specify something to restore", "restore_already_installed_app": "An app is already installed with the id '{app:s}'", "restore_app_failed": "Unable to restore the app '{app:s}'", @@ -459,6 +491,7 @@ "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", "unbackup_app": "App '{app:s}' will not be saved", @@ -474,12 +507,14 @@ "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", + "user_alread_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/user.py b/src/yunohost/user.py index a38f0b4c5..b4790926b 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -26,6 +26,7 @@ import os import re import pwd +import grp import json import crypt import random @@ -123,7 +124,8 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Validate uniqueness of username and mail in LDAP auth.validate_uniqueness({ 'uid': username, - 'mail': mail + 'mail': mail, + 'cn': username }) # Validate uniqueness of username in system users @@ -150,7 +152,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Get random UID/GID all_uid = {x.pw_uid for x in pwd.getpwall()} - all_gid = {x.pw_gid for x in pwd.getpwall()} + all_gid = {x.gr_gid for x in grp.getgrall()} uid_guid_found = False while not uid_guid_found: @@ -160,7 +162,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Adapt values for LDAP fullname = '%s %s' % (firstname, lastname) attr_dict = { - 'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount'], + 'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh'], 'givenName': firstname, 'sn': lastname, 'displayName': fullname, @@ -201,25 +203,26 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Invalidate passwd to take user creation into account subprocess.call(['nscd', '-i', 'passwd']) - # Update SFTP user group - memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid'] - memberlist.append(username) - if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}): - 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) - app_ssowatconf(auth) - # TODO: Send a welcome mail to user - logger.success(m18n.n('user_created')) - hook_callback('post_user_create', - args=[username, mail, password, firstname, lastname]) + 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) + app_ssowatconf(auth) + # TODO: Send a welcome mail to user + logger.success(m18n.n('user_created')) + # Create group for user and add to group 'ALL' + user_group_add(auth, groupname=username, gid=uid) + user_group_update(auth, groupname=username, add_user=username, force=True) + user_group_update(auth, 'ALL', add_user=username, force=True) - return {'fullname': fullname, 'username': username, 'mail': mail} + hook_callback('post_user_create', + args=[username, mail, password, firstname, lastname]) + + return {'fullname': fullname, 'username': username, 'mail': mail} raise YunohostError('user_creation_failed') @@ -242,19 +245,24 @@ def user_delete(operation_logger, auth, username, purge=False): # Invalidate passwd to take user deletion into account subprocess.call(['nscd', '-i', 'passwd']) - # Update SFTP user group - memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid'] - try: - memberlist.remove(username) - except: - pass - if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}): - if purge: - subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) - subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) + if purge: + subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) else: raise YunohostError('user_deletion_failed') + user_group_delete(auth, username, force=True) + + group_list = auth.search('ou=groups,dc=yunohost,dc=org', + '(&(objectclass=groupOfNamesYnh)(memberUid=%s))' + % username, ['cn']) + for group in group_list: + user_list = auth.search('ou=groups,dc=yunohost,dc=org', + 'cn=' + group['cn'][0], + ['memberUid'])[0] + user_list['memberUid'].remove(username) + if not auth.update('cn=%s,ou=groups' % group['cn'][0], user_list): + raise YunohostError('group_update_failed') + app_ssowatconf(auth) hook_callback('post_user_delete', args=[username, purge]) @@ -462,6 +470,278 @@ def user_info(auth, username): else: raise YunohostError('user_info_failed') +# +# Group subcategory +# +# +def user_group_list(auth, fields=None): + """ + 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 + + """ + 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 MoulinetteError(errno.EINVAL, + m18n.n('field_invalid', attr)) + else: + attrs = ['cn', 'member'] + + result = auth.search('ou=groups,dc=yunohost,dc=org', + '(objectclass=groupOfNamesYnh)', + attrs) + + 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(".")[0] + pType = v.split("=")[1].split(",")[0].split(".")[1] + 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] + + groupname = entry[group_attr['cn']] + groups[groupname] = entry + return {'groups' : groups} + + +@is_unit_operation([('groupname', 'user')]) +def user_group_add(operation_logger, auth, groupname,gid=None): + """ + Create group + + Keyword argument: + groupname -- Must be unique + + """ + from yunohost.app import app_ssowatconf + from yunohost.permission import _permission_sync_to_user + + operation_logger.start() + + # Validate uniqueness of groupname in LDAP + conflict = auth.get_conflict({ + 'cn': groupname + }, base_dn='ou=groups,dc=yunohost,dc=org') + if conflict: + raise MoulinetteError(errno.EEXIST, m18n.n('group_name_already_exist', name=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 MoulinetteError(errno.EEXIST, m18n.n('system_groupname_exists')) + + if not gid: + # Get random GID + all_gid = {x.gr_gid for x in grp.getgrall()} + + uid_guid_found = False + while not uid_guid_found: + gid = str(random.randint(200, 99999)) + uid_guid_found = gid not in all_gid + + attr_dict = { + 'objectClass': ['top', 'groupOfNamesYnh', 'posixGroup'], + 'cn': groupname, + 'gidNumber': gid, + } + + if auth.add('cn=%s,ou=groups' % groupname, attr_dict): + _permission_sync_to_user(auth) + app_ssowatconf(auth) + logger.success(m18n.n('group_created')) + return {'name': groupname} + + raise MoulinetteError(169, m18n.n('group_creation_failed')) + + +@is_unit_operation([('groupname', 'user')]) +def user_group_delete(operation_logger, auth, groupname, force=False): + """ + Delete user + + Keyword argument: + groupname -- Groupname to delete + + """ + from yunohost.app import app_ssowatconf + from yunohost.permission import _permission_sync_to_user + + if not force and (groupname == 'ALL' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): + raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname)) + + operation_logger.start() + if not auth.remove('cn=%s,ou=groups' % groupname): + raise MoulinetteError(169, m18n.n('group_deletion_failed')) + + _permission_sync_to_user(auth) + app_ssowatconf(auth) + logger.success(m18n.n('group_deleted')) + + +@is_unit_operation([('groupname', 'user')]) +def user_group_update(operation_logger, auth, groupname, add_user=None, remove_user=None, force=False): + """ + Update user informations + + Keyword argument: + groupname -- Groupname to update + add_user -- User to add in group + remove_user -- User to remove in group + + """ + + from yunohost.app import app_ssowatconf + from yunohost.permission import _permission_sync_to_user + + attrs_to_fetch = ['member'] + + if (groupname == 'ALL' or groupname == 'admins') and not force: + raise MoulinetteError(errno.EINVAL, m18n.n('edit_group_not_allowed', group=groupname)) + + # Populate group informations + result = auth.search(base='ou=groups,dc=yunohost,dc=org', + filter='cn=' + groupname, attrs=attrs_to_fetch) + if not result: + raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname)) + group = result[0] + + new_group_list = {'member': set(), 'memberUid': set()} + if 'member' in group: + new_group_list['member'] = set(group['member']) + else: + group['member'] = [] + + user_l = user_list(auth, ['uid'])['users'] + + if add_user: + if not isinstance(add_user, list): + add_user = [add_user] + for user in add_user: + if not user in user_l: + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=user)) + userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" + if userDN in group['member']: + logger.warning(m18n.n('user_alread_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] + for user in remove_user: + userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" + if user == groupname: + raise MoulinetteError(errno.EINVAL, + m18n.n('remove_user_of_group_not_allowed', user=user, group=groupname)) + if 'member' in group and userDN in group['member']: + new_group_list['member'].remove(userDN) + else: + 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) + + operation_logger.start() + + if new_group_list['member'] != set(group['member']): + if not auth.update('cn=%s,ou=groups' % groupname, new_group_list): + raise MoulinetteError(169, m18n.n('group_update_failed')) + + _permission_sync_to_user(auth) + logger.success(m18n.n('group_updated')) + app_ssowatconf(auth) + return user_group_info(auth, groupname) + + +def user_group_info(auth, groupname): + """ + Get user informations + + Keyword argument: + groupname -- Groupname to get informations + + """ + group_attrs = [ + 'cn', 'member', 'permission' + ] + result = auth.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs) + + if not result: + raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname)) + else: + group = result[0] + + result_dict = { + 'groupname': group['cn'][0], + 'member': None + } + if 'member' in group: + result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} + return result_dict + +# +# Permission subcategory +# +# +import yunohost.permission + +def user_permission_list(auth, app=None, permission=None, username=None, group=None): + return yunohost.permission.user_permission_list(auth, app, permission, username, group) + +@is_unit_operation([('app', 'user')]) +def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None): + return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, + add_username=username, add_group=group, + del_username=None, del_group=None) + +@is_unit_operation([('app', 'user')]) +def user_permission_remove(operation_logger, auth, app, permission="main", username=None, group=None): + return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, + add_username=None, add_group=None, + del_username=username, del_group=group) + +@is_unit_operation([('app', 'user')]) +def user_permission_clear(operation_logger, auth, app, permission=None): + return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission) + # # SSH subcategory # From fbaddd900283cb57cd4da504e704278d31e84ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 22:53:31 +0100 Subject: [PATCH 04/58] Implement permission management --- data/other/ldap_scheme.yml | 8 +- locales/en.json | 3 + src/yunohost/permission.py | 490 +++++++++++++++++++++++++++++++++++++ src/yunohost/user.py | 8 +- 4 files changed, 501 insertions(+), 8 deletions(-) create mode 100644 src/yunohost/permission.py diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index d30c4915c..11504bbe8 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -51,8 +51,8 @@ children: objectClass: - posixGroup - top - cn=ALL,ou=groups: - cn: ALL + cn=all_users,ou=groups: + cn: all_users gidNumber: "4002" objectClass: - posixGroup @@ -66,7 +66,7 @@ depends_children: - posixGroup - permissionYnh groupPermission: - - "cn=ALL,ou=groups,dc=yunohost,dc=org" + - "cn=all_users,ou=groups,dc=yunohost,dc=org" cn=main.metronome,ou=permission: cn: main.metronome gidNumber: "5002" @@ -74,4 +74,4 @@ depends_children: - posixGroup - permissionYnh groupPermission: - - "cn=ALL,ou=groups,dc=yunohost,dc=org" + - "cn=all_users,ou=groups,dc=yunohost,dc=org" diff --git a/locales/en.json b/locales/en.json index 5b78cdcd7..51cb01b0b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -271,6 +271,7 @@ "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", "log_user_permission_add": "Update '{}' permission", + "log_user_permission_remove": "Update '{}' permisson", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_migrations_migrate_backward": "Migrate backward", @@ -357,6 +358,7 @@ "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 by 'yunohost user permission add -u USER' before to remove 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", @@ -400,6 +402,7 @@ "permission_name_not_valid": "Permission name '{permission:s}' not valid", "permission_update_failed": "Permission update failed", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", + "permission_update_nothing_to_do": "Permission update nothing to do", "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", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py new file mode 100644 index 000000000..df53e9b6b --- /dev/null +++ b/src/yunohost/permission.py @@ -0,0 +1,490 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2014 YUNOHOST.ORG + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" + +""" yunohost_permission.py + + Manage permissions +""" + +import errno +import grp +import random + +from moulinette import m18n +from moulinette.core import MoulinetteError +from moulinette.utils.log import getActionLogger +from yunohost.user import user_list, user_group_list +from yunohost.app import app_ssowatconf +from yunohost.log import is_unit_operation + +logger = getActionLogger('yunohost.user') + +def user_permission_list(auth, app=None, permission=None, username=None, group=None): + """ + 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 + + """ + + user_l = user_list(auth, ['uid'])['users'] + + 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(group, list): + group = [group] + if isinstance(username, list): + group.extend(username) + else: + group.append(username) + group = filter(None, group) + + permissions = {} + + result = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', permission_attrs) + + for res in result: + permission_name = res['cn'][0].split('.')[0] + try: + app_name = res['cn'][0].split('.')[1] + except: + logger.warning(m18n.n('permission_name_not_valid', permission=per)) + 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 diffined a specific permission, user or group + if app and not app_name in app: + continue + if permission and not permission_name in permission: + continue + if group and not set(group) & set(group_name): + continue + + if not app_name 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) + + return {'permissions': permissions} + + +def user_permission_update(operation_logger, auth, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None): + """ + 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 + + """ + from yunohost.hook import hook_callback + from yunohost.user import user_group_list + + if permission: + if not isinstance(permission, list): + permission = [permission] + else: + permission = ["main"] + + 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 not g in user_group_list(auth, ['cn'])['groups']: + raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=g)) + for u in add_username: + if not u in user_list(auth, ['uid'])['users']: + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=u)) + for g in del_group: + if not g in user_group_list(auth, ['cn'])['groups']: + raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=g)) + for u in del_username: + if not u in user_list(auth, ['uid'])['users']: + raise MoulinetteError(errno.EINVAL, m18n.n('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 MoulinetteError(errno.EINVAL, m18n.n('edit_permission_with_group_all_users_not_allowed')) + + # Populate permission informations + permission_attrs = [ + 'cn', + 'groupPermission', + ] + result = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', permission_attrs) + result = {p['cn'][0]: p for p in result} + + new_per_dict = {} + + for a in app: + for per in permission: + permission_name = per + '.' + a + if not permission_name in result: + raise MoulinetteError(errno.EINVAL, m18n.n('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']) + + for g in del_group: + if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: + raise MoulinetteError(errno.EINVAL, m18n.n('need_define_permission_before')) + group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' + if not group_name in new_per_dict[permission_name]: + logger.warning(m18n.n('group_alread_disallowed', permission=per, app=a, group=g)) + else: + new_per_dict[permission_name].remove(group_name) + + 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_alread_allowed', permission=per, app=a, group=g)) + else: + new_per_dict[permission_name].add(group_name) + + 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 auth.update('cn=%s,ou=permission' % per, {'groupPermission': val}): + p = per.split('.') + logger.success(m18n.n('permission_updated', permission=p[0], app=p[1])) + else: + raise MoulinetteError(169, m18n.n('permission_update_failed')) + + _permission_sync_to_user(auth) + + for a in app: + allowed_users = set() + disallowed_users = set() + group_list = user_group_list(auth, ['member'])['groups'] + + for g in add_group: + if 'members' in group_list[g]: + 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']) + + 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]) + + app_ssowatconf(auth) + return user_permission_list(auth, app, permission) + + +def user_permission_clear(operation_logger, auth, app=[], permission=None): + """ + Reset the permission for a 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 (all by default) + group -- Groupname to get informations (all by default) + + """ + from yunohost.hook import hook_callback + + if permission: + if not isinstance(permission, list): + permission = [permission] + else: + permission = ["main"] + + default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} + + # Populate permission informations + permission_attrs = [ + 'cn', + 'groupPermission', + ] + result = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', permission_attrs) + result = {p['cn'][0]: p for p in result} + + for a in app: + for per in permission: + permission_name = per + '.' + a + if not permission_name in result: + raise MoulinetteError(errno.EINVAL, m18n.n('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 auth.update('cn=%s,ou=permission' % permission_name, default_permission): + logger.success(m18n.n('permission_updated', permission=per, app=a)) + else: + raise MoulinetteError(169, m18n.n('permission_update_failed')) + + _permission_sync_to_user(auth) + + for a in app: + permission_name = 'main.' + a + result = auth.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]) + + app_ssowatconf(auth) + return user_permission_list(auth, app, permission) + + +@is_unit_operation(['permission','app']) +def permission_add(operation_logger, auth, app, permission, url=None): + """ + 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) + url -- list of url to specify for the permission + + """ + from yunohost.domain import _normalize_domain_path + + # Validate uniqueness of permission in LDAP + permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + conflict = auth.get_conflict({ + 'cn': permission_name + }, base_dn='ou=permission,dc=yunohost,dc=org') + if conflict: + raise MoulinetteError(errno.EEXIST, m18n.n('permission_already_exist', permission=permission, app=app)) + + # Get random GID + all_gid = {x.gr_gid for x in grp.getgrall()} + + uid_guid_found = False + while not uid_guid_found: + gid = str(random.randint(200, 99999)) + uid_guid_found = gid not in all_gid + + attr_dict = { + 'objectClass': ['top', 'permissionYnh', 'posixGroup'], + 'cn': permission_name, + 'gidNumber': gid, + 'groupPermission': 'cn=all_users,ou=groups,dc=yunohost,dc=org' + } + + if url: + attr_dict['URL'] = [] + for u in url: + domain = u[:u.index('/')] + path = u[u.index('/'):] + domain, path = _normalize_domain_path(domain, path) + attr_dict['URL'].append(domain + path) + + operation_logger.start() + if auth.add('cn=%s,ou=permission' % permission_name, attr_dict): + _permission_sync_to_user(auth) + logger.success(m18n.n('permission_created', permission=permission, app=app)) + return user_permission_list(auth, app, permission) + + raise MoulinetteError(169, m18n.n('premission_creation_failled')) + + +@is_unit_operation(['permission','app']) +def permission_update(operation_logger, auth, app, permission, add_url=None, remove_url=None): + """ + Update 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 + + """ + from yunohost.domain import _normalize_domain_path + + permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + + # Populate permission informations + result = auth.search(base='ou=permission,dc=yunohost,dc=org', + filter='cn=' + permission_name, attrs=['URL']) + if not result: + raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=permission, app=app)) + permission_obj = result[0] + + if not 'URL' in permission_obj: + permission_obj['URL'] = [] + + url = set(permission_obj['URL']) + + 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 url == set(permission_obj['URL']): + logger.warning(m18n.n('permission_update_nothing_to_do')) + return user_permission_list(auth, app, permission) + + operation_logger.start() + if auth.update('cn=%s,ou=permission' % permission_name, {'cn':permission_name, 'URL': url}): + _permission_sync_to_user(auth) + logger.success(m18n.n('permission_updated', permission=permission, app=app)) + return user_permission_list(auth, app, permission) + + raise MoulinetteError(169, m18n.n('premission_update_failled')) + + +@is_unit_operation(['permission','app']) +def permission_remove(operation_logger, auth, app, permission, force=False): + """ + Remove a permission for a specific application + + Keyword argument: + app -- an application OR sftp, xmpp (metronome), mail + permission -- name of the permission ("main" by default) + + """ + + if permission == "main" and not force: + raise MoulinetteError(errno.EPERM, m18n.n('remove_main_permission_not_allowed')) + + operation_logger.start() + if not auth.remove('cn=%s,ou=permission' % str(permission + '.' + app)): + raise MoulinetteError(169, m18n.n('permission_deletion_failed', permission=permission, app=app)) + _permission_sync_to_user(auth) + logger.success(m18n.n('permission_deleted', permission=permission, app=app)) + + +def _permission_sync_to_user(auth): + """ + Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link + """ + import os + + permission_attrs = [ + 'cn', + 'member', + 'permission', + ] + group_info = auth.search('ou=groups,dc=yunohost,dc=org', + '(objectclass=groupOfNamesYnh)', permission_attrs) + user_permission={} + + for group in group_info: + if 'permission' not in group: + continue + if not 'member' in group: + continue + for permission in group['permission']: + permission = permission.split("=")[1].split(",")[0] + if not permission in user_permission: + user_permission[permission] = set() + for member in group['member']: + user_permission[permission].add(member) + + for per in auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', ['cn', 'inheritPermission']): + if per['cn'][0] in user_permission: + val = set(user_permission[per['cn'][0]]) + else: + # If the new value and the old value à empty nothing to do + if not 'inheritPermission' in per: + continue + val = set() + if 'inheritPermission' in per and val == set(per['inheritPermission']): + continue + uid_val = [v.split("=")[1].split(",")[0] for v in val] + inheritPermission = {'inheritPermission': val, 'memberUid': uid_val} + if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): + raise MoulinetteError(169, m18n.n('permission_update_failed')) + + # Reload unscd because if not the group is not updated in the system from LDAP + os.system('systemctl restart unscd') diff --git a/src/yunohost/user.py b/src/yunohost/user.py index b4790926b..a2ff5b5a9 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -214,10 +214,10 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas app_ssowatconf(auth) # TODO: Send a welcome mail to user logger.success(m18n.n('user_created')) - # Create group for user and add to group 'ALL' + # Create group for user and add to group 'all_users' user_group_add(auth, groupname=username, gid=uid) user_group_update(auth, groupname=username, add_user=username, force=True) - user_group_update(auth, 'ALL', add_user=username, force=True) + user_group_update(auth, 'all_users', add_user=username, force=True) hook_callback('post_user_create', args=[username, mail, password, firstname, lastname]) @@ -598,7 +598,7 @@ def user_group_delete(operation_logger, auth, groupname, force=False): from yunohost.app import app_ssowatconf from yunohost.permission import _permission_sync_to_user - if not force and (groupname == 'ALL' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): + if not force and (groupname == 'all_users' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname)) operation_logger.start() @@ -627,7 +627,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u attrs_to_fetch = ['member'] - if (groupname == 'ALL' or groupname == 'admins') and not force: + if (groupname == 'all_users' or groupname == 'admins') and not force: raise MoulinetteError(errno.EINVAL, m18n.n('edit_group_not_allowed', group=groupname)) # Populate group informations From c1dc117863b74b91e641026792f6833aa3f64077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 23:01:48 +0100 Subject: [PATCH 05/58] Implement permission for app --- data/actionsmap/yunohost.yml | 15 +- src/yunohost/app.py | 199 ++++++++------------------- src/yunohost/domain.py | 2 +- src/yunohost/tests/test_changeurl.py | 2 +- src/yunohost/user.py | 4 +- 5 files changed, 70 insertions(+), 152 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index afd2ed3b3..360b0780a 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -338,7 +338,7 @@ user: arguments: app: help: Application to manage the permission - nargs: "*" + nargs: "+" -p: full: --permission help: Name of permission (main by default) @@ -368,7 +368,7 @@ user: arguments: app: help: Application to manage the permission - nargs: "*" + nargs: "+" -p: full: --permission help: Name of permission (main by default) @@ -398,7 +398,7 @@ user: arguments: app: help: Application to manage the permission - nargs: "*" + nargs: "+" -p: full: --permission help: Name of permission (main by default) @@ -711,6 +711,9 @@ app: map: action_help: List apps by domain api: GET /appsmap + configuration: + authenticate: all + authenticator: ldap-anonymous arguments: -a: full: --app @@ -731,7 +734,6 @@ app: api: POST /apps configuration: authenticate: all - authenticator: ldap-anonymous arguments: app: help: Name, local path or git URL of the app to install @@ -756,7 +758,6 @@ app: api: DELETE /apps/ configuration: authenticate: all - authenticator: ldap-anonymous arguments: app: help: App(s) to delete @@ -785,7 +786,6 @@ app: api: PUT /apps//changeurl configuration: authenticate: all - authenticator: ldap-anonymous arguments: app: help: Target app instance name @@ -931,7 +931,6 @@ app: api: PUT /access configuration: authenticate: all - authenticator: ldap-anonymous arguments: apps: nargs: "+" @@ -945,7 +944,6 @@ app: api: DELETE /access configuration: authenticate: all - authenticator: ldap-anonymous arguments: apps: nargs: "+" @@ -959,7 +957,6 @@ app: api: POST /access configuration: authenticate: all - authenticator: ldap-anonymous arguments: apps: nargs: "+" diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3925a86db..72755a5c5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -381,7 +381,7 @@ def app_info(app, show_status=False, raw=False): return info -def app_map(app=None, raw=False, user=None): +def app_map(auth, app=None, raw=False, user=None): """ List apps by domain @@ -391,6 +391,8 @@ def app_map(app=None, raw=False, user=None): app -- Specific app to map """ + from yunohost.permission import user_permission_list + apps = [] result = {} @@ -410,11 +412,9 @@ def app_map(app=None, raw=False, user=None): if 'no_sso' in app_settings: # I don't think we need to check for the value here continue if user is not None: - if ('mode' not in app_settings - or ('mode' in app_settings - and app_settings['mode'] == 'private')) \ - and 'allowed_users' in app_settings \ - and user not in app_settings['allowed_users'].split(','): + if not auth.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), + attrs=['cn']): continue domain = app_settings['domain'] @@ -446,6 +446,7 @@ def app_change_url(operation_logger, auth, 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 installed = _is_installed(app) if not installed: @@ -535,6 +536,8 @@ def app_change_url(operation_logger, auth, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) + permission_update(auth, app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path]) + app_ssowatconf(auth) # avoid common mistakes @@ -707,6 +710,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on """ 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 # Fetch or extract sources try: @@ -769,7 +773,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on _check_manifest_requirements(manifest, app_id) # Check if app can be forked - instance_number = _installed_instance_number(app_id, last=True) + 1 + instance_number = _installed_instance_number(auth, app_id, last=True) + 1 if instance_number > 1: if 'multi_instance' not in manifest or not is_true(manifest['multi_instance']): raise YunohostError('app_already_installed', app=app_id) @@ -828,6 +832,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) + # Create permission before the install (useful if the install script redefine the permission) + permission_add(auth, app=app_instance_name, permission="main") + # Execute the app install script install_retcode = 1 try: @@ -896,6 +903,13 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on os.system('chown -R root: %s' % app_setting_path) os.system('chown -R admin: %s/scripts' % app_setting_path) + # Add path in permission if it's defined in the app install script + app_settings = _get_app_settings(app_instance_name) + domain = app_settings['domain'] + path = app_settings.get('path', '/') + if domain and path: + permission_update(auth, app_instance_name, permission="main", add_url=[domain+path]) + app_ssowatconf(auth) logger.success(m18n.n('installation_complete')) @@ -913,6 +927,7 @@ def app_remove(operation_logger, auth, app): """ from yunohost.hook import hook_exec, hook_remove, hook_callback + from yunohost.permission import permission_remove if not _is_installed(app): raise YunohostError('app_not_installed', app=app) @@ -954,10 +969,18 @@ def app_remove(operation_logger, auth, app): shutil.rmtree(app_setting_path) shutil.rmtree('/tmp/yunohost_remove') hook_remove(app) + + # Remove all permission in LDAP + result = auth.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_remove(auth, app, l.split('.')[0], force=True) + app_ssowatconf(auth) - -def app_addaccess(auth, apps, users=[]): +@is_unit_operation(['permission','app']) +def app_addaccess(operation_logger, auth, apps, users=[]): """ Grant access right to users (everyone by default) @@ -966,64 +989,17 @@ def app_addaccess(auth, apps, users=[]): apps """ - from yunohost.user import user_list, user_info - from yunohost.hook import hook_callback + from yunohost.permission import user_permission_update - result = {} + permission = user_permission_update(operation_logger, auth, app=apps, permission="main", add_username=users) - if not users: - users = user_list(auth)['users'].keys() - elif not isinstance(users, list): - users = [users, ] - if not isinstance(apps, list): - apps = [apps, ] - - for app in apps: - - app_settings = _get_app_settings(app) - if not app_settings: - continue - - if 'mode' not in app_settings: - app_setting(app, 'mode', 'private') - app_settings['mode'] = 'private' - - if app_settings['mode'] == 'private': - - # Start register change on system - related_to = [('app', app)] - operation_logger = OperationLogger('app_addaccess', related_to) - operation_logger.start() - - allowed_users = set() - if 'allowed_users' in app_settings and app_settings['allowed_users']: - allowed_users = set(app_settings['allowed_users'].split(',')) - - for allowed_user in users: - if allowed_user not in allowed_users: - try: - user_info(auth, allowed_user) - except YunohostError: - logger.warning(m18n.n('user_unknown', user=allowed_user)) - continue - allowed_users.add(allowed_user) - operation_logger.related_to.append(('user', allowed_user)) - - operation_logger.flush() - new_users = ','.join(allowed_users) - app_setting(app, 'allowed_users', new_users) - hook_callback('post_app_addaccess', args=[app, new_users]) - - operation_logger.success() - - result[app] = allowed_users - - app_ssowatconf(auth) + result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': result} -def app_removeaccess(auth, apps, users=[]): +@is_unit_operation(['permission','app']) +def app_removeaccess(operation_logger, auth, apps, users=[]): """ Revoke access right to users (everyone by default) @@ -1032,59 +1008,17 @@ def app_removeaccess(auth, apps, users=[]): apps """ - from yunohost.user import user_list - from yunohost.hook import hook_callback + from yunohost.permission import user_permission_update - result = {} + permission = user_permission_update(operation_logger, auth, app=apps, permission="main", del_username=users) - remove_all = False - if not users: - remove_all = True - elif not isinstance(users, list): - users = [users, ] - if not isinstance(apps, list): - apps = [apps, ] - - for app in apps: - app_settings = _get_app_settings(app) - if not app_settings: - continue - allowed_users = set() - - if app_settings.get('skipped_uris', '') != '/': - - # Start register change on system - related_to = [('app', app)] - operation_logger = OperationLogger('app_removeaccess', related_to) - operation_logger.start() - - if remove_all: - pass - elif 'allowed_users' in app_settings: - for allowed_user in app_settings['allowed_users'].split(','): - if allowed_user not in users: - allowed_users.add(allowed_user) - else: - for allowed_user in user_list(auth)['users'].keys(): - if allowed_user not in users: - allowed_users.add(allowed_user) - - operation_logger.related_to += [('user', x) for x in allowed_users] - operation_logger.flush() - new_users = ','.join(allowed_users) - app_setting(app, 'allowed_users', new_users) - hook_callback('post_app_removeaccess', args=[app, new_users]) - - result[app] = allowed_users - - operation_logger.success() - - app_ssowatconf(auth) + result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': result} -def app_clearaccess(auth, apps): +@is_unit_operation(['permission','app']) +def app_clearaccess(operation_logger, auth, apps): """ Reset access rights for the app @@ -1092,32 +1026,9 @@ def app_clearaccess(auth, apps): apps """ - from yunohost.hook import hook_callback + from yunohost.permission import user_permission_clear - if not isinstance(apps, list): - apps = [apps] - - for app in apps: - app_settings = _get_app_settings(app) - if not app_settings: - continue - - # Start register change on system - related_to = [('app', app)] - operation_logger = OperationLogger('app_clearaccess', related_to) - operation_logger.start() - - if 'mode' in app_settings: - app_setting(app, 'mode', delete=True) - - if 'allowed_users' in app_settings: - app_setting(app, 'allowed_users', delete=True) - - hook_callback('post_app_clearaccess', args=[app]) - - operation_logger.success() - - app_ssowatconf(auth) + user_permission_clear(operation_logger, auth, app=apps, permission="main") def app_debug(app): @@ -1166,8 +1077,9 @@ def app_makedefault(operation_logger, auth, app, domain=None): raise YunohostError('domain_unknown') operation_logger.start() - if '/' in app_map(raw=True)[domain]: - raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, other_app=app_map(raw=True)[domain]["/"]["id"]) + if '/' in app_map(auth, raw=True)[domain]: + raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, + other_app=app_map(auth, raw=True)[domain]["/"]["id"])) try: with open('/etc/ssowat/conf.json.persistent') as json_conf: @@ -1312,7 +1224,7 @@ def app_checkurl(auth, url, app=None): domain, path = _normalize_domain_path(domain, path) - apps_map = app_map(raw=True) + apps_map = app_map(auth, raw=True) if domain not in domain_list(auth)['domains']: raise YunohostError('domain_unknown') @@ -1379,6 +1291,7 @@ def app_ssowatconf(auth): """ from yunohost.domain import domain_list, _get_maindomain from yunohost.user import user_list + from yunohost.permission import user_permission_list main_domain = _get_maindomain() domains = domain_list(auth)['domains'] @@ -1438,6 +1351,13 @@ def app_ssowatconf(auth): 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(auth)['permissions'].values(): + for p in a.values(): + if 'URL' in p: + for u in p['URL']: + permission[u] = p['allowed_users'] + conf_dict = { 'portal_domain': main_domain, 'portal_path': '/yunohost/sso/', @@ -1456,8 +1376,9 @@ def app_ssowatconf(auth): 'protected_regex': protected_regex, 'redirected_urls': redirected_urls, 'redirected_regex': redirected_regex, - 'users': {username: app_map(user=username) + 'users': {username: app_map(auth, user=username) for username in user_list(auth)['users'].keys()}, + 'permission': permission, } with open('/etc/ssowat/conf.json', 'w+') as f: @@ -1985,7 +1906,7 @@ def _fetch_app_from_git(app): return manifest, extracted_app_folder -def _installed_instance_number(app, last=False): +def _installed_instance_number(auth, app, last=False): """ Check if application is installed and return instance number @@ -2017,7 +1938,7 @@ def _installed_instance_number(app, last=False): else: instance_number_list = [] - instances_dict = app_map(app=app, raw=True) + instances_dict = app_map(auth, app=app, raw=True) for key, domain in instances_dict.items(): for key, path in domain.items(): instance_number_list.append(path['instance']) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 16d391168..1d1c66bb2 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -246,7 +246,7 @@ def _get_conflicting_apps(auth, domain, path): from yunohost.app import app_map # Fetch apps map - apps_map = app_map(raw=True) + apps_map = app_map(auth, raw=True) # Loop through all apps to check if path is taken by one of them conflicts = [] diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index e11acdb59..d37d3ed48 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -32,7 +32,7 @@ def install_changeurl_app(path): def check_changeurl_app(path): - appmap = app_map(raw=True) + appmap = app_map(auth, raw=True) assert path in appmap[maindomain].keys() diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a2ff5b5a9..32cb6f684 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -522,8 +522,8 @@ def user_group_list(auth, fields=None): elif attr == "permission": entry[group_attr[attr]] = {} for v in values: - permission = v.split("=")[1].split(",")[0].split(".")[0] - pType = v.split("=")[1].split(",")[0].split(".")[1] + 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: From c5c482c16c85717d620cb626128682f02947a194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 23:02:10 +0100 Subject: [PATCH 06/58] Use permission for all services --- 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 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/templates/dovecot/dovecot-ldap.conf b/data/templates/dovecot/dovecot-ldap.conf index 221fe85c1..c7c9785fd 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)) -pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)) +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)) default_pass_scheme = SSHA diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/data/templates/metronome/domain.tpl.cfg.lua index 2c7fd7489..2ee9cfaae 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 }}))", + filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.metronome,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 bd3576dec..9f6f94e6d 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)) +query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,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 a9ef52cf9..5e7d3a6c1 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)) +query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) result_attribute = maildrop From 05ba65b8d350945c7a56f6638da12168acc175d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 28 Nov 2018 09:58:39 +0100 Subject: [PATCH 07/58] Fix actionmap --- 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 360b0780a..2be9b2b2f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -287,7 +287,7 @@ user: ### user_group_info() info: action_help: Get group information - api: GET /users/ + api: GET /users/groups/ configuration: authenticate: all authenticator: ldap-anonymous From ad628b7620826b56900bc3d6b4428fda54c2e596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 28 Nov 2018 08:20:55 +0100 Subject: [PATCH 08/58] Use root UID to authenticate to LDAP --- data/actionsmap/yunohost.yml | 37 +++++++++++++++++++++++++++++++-- data/templates/slapd/slapd.conf | 3 +++ src/yunohost/tools.py | 10 ++++----- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2be9b2b2f..9676baf52 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -43,12 +43,19 @@ _global: parameters: uri: ldap://localhost:389 base_dn: dc=yunohost,dc=org - user_rdn: cn=admin + user_rdn: cn=admin,dc=yunohost,dc=org ldap-anonymous: vendor: ldap parameters: uri: ldap://localhost:389 base_dn: dc=yunohost,dc=org + as-root: + vendor: ldap + parameters: + # We can get this uri by (urllib.quote_plus('/var/run/slapd/ldapi') + uri: ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi + base_dn: dc=yunohost,dc=org + user_rdn: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth argument_auth: true arguments: -v: @@ -84,6 +91,7 @@ user: api: POST /users configuration: authenticate: all + authenticator: as-root arguments: username: help: The unique username to create @@ -142,6 +150,7 @@ user: api: DELETE /users/ configuration: authenticate: all + authenticator: as-root arguments: username: help: Username to delete @@ -157,6 +166,7 @@ user: api: PUT /users/ configuration: authenticate: all + authenticator: as-root arguments: username: help: Username to update @@ -238,6 +248,7 @@ user: api: POST /users/groups configuration: authenticate: all + authenticator: as-root arguments: groupname: help: The unique group name to add @@ -252,6 +263,7 @@ user: api: DELETE /users/groups/ configuration: authenticate: all + authenticator: as-root arguments: groupname: help: Username to delete @@ -264,6 +276,7 @@ user: api: PUT /users/groups/ configuration: authenticate: all + authenticator: as-root arguments: groupname: help: Username to update @@ -335,6 +348,7 @@ user: api: POST /users/permission/ configuration: authenticate: all + authenticator: as-root arguments: app: help: Application to manage the permission @@ -365,6 +379,7 @@ user: api: PUT /users/permission/ configuration: authenticate: all + authenticator: as-root arguments: app: help: Application to manage the permission @@ -395,6 +410,7 @@ user: api: DELETE /users/permission/ configuration: authenticate: all + authenticator: as-root arguments: app: help: Application to manage the permission @@ -414,6 +430,7 @@ user: api: POST /users/ssh/enable configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -426,6 +443,7 @@ user: api: POST /users/ssh/disable configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -438,6 +456,7 @@ user: api: GET /users/ssh/keys configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -450,6 +469,7 @@ user: api: POST /users/ssh/key configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -467,6 +487,7 @@ user: api: DELETE /users/ssh/key configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -497,6 +518,7 @@ domain: api: POST /domains configuration: authenticate: all + authenticator: as-root arguments: domain: help: Domain name to add @@ -515,6 +537,7 @@ domain: api: DELETE /domains/ configuration: authenticate: all + authenticator: as-root arguments: domain: help: Domain to delete @@ -734,6 +757,7 @@ app: api: POST /apps configuration: authenticate: all + authenticator: as-root arguments: app: help: Name, local path or git URL of the app to install @@ -758,6 +782,7 @@ app: api: DELETE /apps/ configuration: authenticate: all + authenticator: as-root arguments: app: help: App(s) to delete @@ -768,7 +793,7 @@ app: api: PUT /upgrade/apps configuration: authenticate: all - authenticator: ldap-anonymous + authenticator: as-root arguments: app: help: App(s) to upgrade (default all) @@ -786,6 +811,7 @@ app: api: PUT /apps//changeurl configuration: authenticate: all + authenticator: as-root arguments: app: help: Target app instance name @@ -931,6 +957,7 @@ app: api: PUT /access configuration: authenticate: all + authenticator: as-root arguments: apps: nargs: "+" @@ -944,6 +971,7 @@ app: api: DELETE /access configuration: authenticate: all + authenticator: as-root arguments: apps: nargs: "+" @@ -957,6 +985,7 @@ app: api: POST /access configuration: authenticate: all + authenticator: as-root arguments: apps: nargs: "+" @@ -1659,6 +1688,7 @@ tools: api: POST /ldap configuration: authenticate: all + authenticator: as-root ### tools_adminpw() adminpw: @@ -1666,6 +1696,7 @@ tools: api: PUT /adminpw configuration: authenticate: all + authenticator: as-root arguments: -n: full: --new-password @@ -1683,6 +1714,7 @@ tools: - PUT /domains/main configuration: authenticate: all + authenticator: as-root arguments: -n: full: --new-domain @@ -1773,6 +1805,7 @@ tools: shell: configuration: authenticate: all + authenticator: as-root action_help: Launch a development shell arguments: -c: diff --git a/data/templates/slapd/slapd.conf b/data/templates/slapd/slapd.conf index 4acebe97e..5e76bdc01 100644 --- a/data/templates/slapd/slapd.conf +++ b/data/templates/slapd/slapd.conf @@ -82,6 +82,7 @@ checkpoint 512 30 # These access lines apply to database #1 only access to attrs=userPassword,shadowLastChange by dn="cn=admin,dc=yunohost,dc=org" write + by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by anonymous auth by self write by * none @@ -91,6 +92,7 @@ access to attrs=userPassword,shadowLastChange # Others should be able to see it. access to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn by dn="cn=admin,dc=yunohost,dc=org" write + by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by self write by * read @@ -109,6 +111,7 @@ access to dn.base="" by * read # can read everything. access to * by dn="cn=admin,dc=yunohost,dc=org" write + by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write by * read diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d58951878..38863686d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -68,11 +68,11 @@ def tools_ldapinit(): """ # Instantiate LDAP Authenticator - auth = init_authenticator(('ldap', 'default'), - {'uri': "ldap://localhost:389", - 'base_dn': "dc=yunohost,dc=org", - 'user_rdn': "cn=admin"}) - auth.authenticate('yunohost') + AUTH_IDENTIFIER = ('ldap', 'as-root') + AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: ldap_map = yaml.load(f) From 4c2ae4fc776b2f0d70e8d55e05507837d93ecdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 28 Nov 2018 22:06:33 +0100 Subject: [PATCH 09/58] Implement permission helper --- data/helpers.d/setting | 70 ++++++++++++++++++++++++++++++++++++++ src/yunohost/app.py | 9 ++++- src/yunohost/permission.py | 5 +-- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index ad036ba4f..3267bf846 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -25,3 +25,73 @@ ynh_app_setting_set() { ynh_app_setting_delete() { sudo yunohost app setting -d "$1" "$2" --quiet } + +# Create a new permission for the app +# +# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--url "url" ["url" ...]] +# | arg: app - the application id +# | 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: url - the url for the the permission +ynh_permission_create() { + declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=url= ) + local app + local permission + local defaultdisallow + local url + ynh_handle_getopts_args "$@" + if [[ -n ${defaultdisallow:-} ]]; then + defaultdisallow=",default_allow=False" + fi + + if [[ -n ${url:-} ]]; then + url=",url=['${url//';'/"','"}']" + fi + yunohost tools shell -c "from yunohost.permission import permission_add; permission_add(auth, '$app', '$permission' ${defaultdisallow:-} ${url:-}, 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 +# | 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 + local permission + ynh_handle_getopts_args "$@" + + yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove(auth, '$app', '$permission')" +} + +# Add a path managed by the SSO +# +# usage: ynh_permission_add_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) +ynh_permission_add_path() { + 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(auth, '$app', '$permission', add_url=['${url//';'/"','"}'])" +} + +# 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) +ynh_permission_del_path() { + 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(auth, '$app', '$permission', remove_url=['${url//';'/"','"}'])" +} diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 72755a5c5..99b688322 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -710,7 +710,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on """ 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 + from yunohost.permission import permission_add, permission_update, permission_remove # Fetch or extract sources try: @@ -867,6 +867,13 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on os.path.join(extracted_app_folder, 'scripts/remove'), args=[app_instance_name], env=env_dict_remove ) + # Remove all permission in LDAP + result = auth.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_remove(auth, app_instance_name, l.split('.')[0], force=True) + if remove_retcode != 0: msg = m18n.n('app_not_properly_removed', app=app_instance_name) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index df53e9b6b..4b77df70f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -316,7 +316,7 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None): @is_unit_operation(['permission','app']) -def permission_add(operation_logger, auth, app, permission, url=None): +def permission_add(operation_logger, auth, app, permission, url=None, default_allow=True): """ Create a new permission for a specific application @@ -348,8 +348,9 @@ def permission_add(operation_logger, auth, app, permission, url=None): 'objectClass': ['top', 'permissionYnh', 'posixGroup'], 'cn': permission_name, 'gidNumber': gid, - 'groupPermission': 'cn=all_users,ou=groups,dc=yunohost,dc=org' } + if default_allow: + attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' if url: attr_dict['URL'] = [] From b01e4b61f5c285ea54dd38c2d3c9c1b3a59fce7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 29 Nov 2018 22:49:45 +0100 Subject: [PATCH 10/58] Implement backup - restore and update test --- data/hooks/backup/05-conf_ldap | 6 ++++-- locales/en.json | 1 + src/yunohost/backup.py | 9 +++++++++ src/yunohost/tests/test_appurl.py | 8 +++++--- src/yunohost/tests/test_backuprestore.py | 6 ++++-- src/yunohost/tests/test_changeurl.py | 6 ++++-- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap index b21103ede..8e593577a 100755 --- a/data/hooks/backup/05-conf_ldap +++ b/data/hooks/backup/05-conf_ldap @@ -13,5 +13,7 @@ backup_dir="${1}/conf/ldap" ynh_backup "/etc/ldap/slapd.conf" "${backup_dir}/slapd.conf" sudo slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" -# Backup the database -sudo slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" +# Backup the database (all but not the permission except the permission for mail, metronome and sftp +sudo slapcat -b dc=yunohost,dc=org \ + -H 'ldap:///dc=yunohost,dc=org???(|(!(objectClass=permissionYnh))(cn=main.mail)(cn=main.metronome)(cn=main.sftp))' \ + -l "${backup_dir}/dc=yunohost-dc=org.ldif" diff --git a/locales/en.json b/locales/en.json index 51cb01b0b..f1fe0252a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -105,6 +105,7 @@ "backup_output_directory_not_empty": "The output directory is not empty", "backup_output_directory_required": "You must provide an output directory for the backup", "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", + "backup_permission": "Backup permission for app {app:s}", "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support php7, your php apps may fail to restore (reason: {error:s})", "backup_running_app_script": "Running backup script of app '{app:s}'…", "backup_running_hooks": "Running backup hooks…", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 0c8445bd6..00e9a1e21 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -685,6 +685,12 @@ class BackupManager(): raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict) self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) + + # backup permissions + 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)) + except: abs_tmp_app_dir = os.path.join(self.work_dir, 'apps/', app) shutil.rmtree(abs_tmp_app_dir, ignore_errors=True) @@ -1279,6 +1285,9 @@ class RestoreManager(): filesystem.chmod(app_settings_new_path, 0o400, 0o400, True) filesystem.chown(app_scripts_new_path, 'admin', None, True) + # Restore permissions + os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) + # Copy the app scripts to a writable temporary folder # FIXME : use 'install -Dm555' or something similar to what's done # in the backup method ? diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index 3a3a1db35..3234fefcb 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -6,9 +6,11 @@ from yunohost.app import app_install, app_remove from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path # Instantiate LDAP Authenticator -auth_identifier = ('ldap', 'ldap-anonymous') -auth_parameters = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'} -auth = init_authenticator(auth_identifier, auth_parameters) +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} +auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) # Get main domain diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 14c479d9a..5775e1612 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -18,8 +18,10 @@ from yunohost.utils.error import YunohostError maindomain = "" # Instantiate LDAP Authenticator -AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous') -AUTH_PARAMETERS = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'} +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} auth = None diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index d37d3ed48..4594dd6b9 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -9,8 +9,10 @@ from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError # Instantiate LDAP Authenticator -AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous') -AUTH_PARAMETERS = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'} +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) From c52ae326313e8449b478740d9b167a2177e091e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 1 Dec 2018 23:21:41 +0100 Subject: [PATCH 11/58] Fix traduction --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index f1fe0252a..6b088537a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -182,6 +182,7 @@ "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.", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", From 9ac60be564aff15b9e07ec348dc2ea7c7525b034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 7 Dec 2018 15:26:04 +0100 Subject: [PATCH 12/58] Sync permission only at end of each operation --- data/helpers.d/setting | 6 ++-- locales/en.json | 1 + src/yunohost/app.py | 22 +++++++------- src/yunohost/permission.py | 30 +++++++++++-------- src/yunohost/user.py | 61 ++++++++++++++++++-------------------- 5 files changed, 62 insertions(+), 58 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 3267bf846..585c7ccd0 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -61,7 +61,7 @@ ynh_permission_remove() { local permission ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove(auth, '$app', '$permission')" + yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove(auth, '$app', '$permission', sync_perm=False)" } # Add a path managed by the SSO @@ -77,7 +77,7 @@ ynh_permission_add_path() { local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', add_url=['${url//';'/"','"}'])" + yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" } # Remove a path managed by the SSO @@ -93,5 +93,5 @@ ynh_permission_del_path() { local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', remove_url=['${url//';'/"','"}'])" + yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" } diff --git a/locales/en.json b/locales/en.json index 6b088537a..3a629ea0e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -403,6 +403,7 @@ "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": "Permission updated", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", "permission_update_nothing_to_do": "Permission update nothing to do", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 99b688322..023007789 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -536,9 +536,7 @@ def app_change_url(operation_logger, auth, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) - permission_update(auth, app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path]) - - app_ssowatconf(auth) + permission_update(auth, app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path], sync_perm=True) # avoid common mistakes if _run_service_command("reload", "nginx") == False: @@ -568,6 +566,7 @@ def app_upgrade(auth, app=[], url=None, file=None): """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback + from yunohost.permission import permission_sync_to_user # Retrieve interface is_api = msettings.get('interface') == 'api' @@ -687,7 +686,7 @@ def app_upgrade(auth, app=[], url=None, file=None): if not upgraded_apps: raise YunohostError('app_no_upgrade') - app_ssowatconf(auth) + permission_sync_to_user(auth) logger.success(m18n.n('upgrade_complete')) @@ -710,7 +709,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on """ 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 + from yunohost.permission import permission_add, permission_update, permission_remove, permission_sync_to_user # Fetch or extract sources try: @@ -915,9 +914,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on domain = app_settings['domain'] path = app_settings.get('path', '/') if domain and path: - permission_update(auth, app_instance_name, permission="main", add_url=[domain+path]) + permission_update(auth, app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) - app_ssowatconf(auth) + permission_sync_to_user(auth) logger.success(m18n.n('installation_complete')) @@ -934,7 +933,7 @@ def app_remove(operation_logger, auth, app): """ from yunohost.hook import hook_exec, hook_remove, hook_callback - from yunohost.permission import permission_remove + from yunohost.permission import permission_remove, permission_sync_to_user if not _is_installed(app): raise YunohostError('app_not_installed', app=app) @@ -982,9 +981,9 @@ def app_remove(operation_logger, auth, 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(auth, app, l.split('.')[0], force=True) + permission_remove(auth, app, l.split('.')[0], force=True, sync_perm=False) - app_ssowatconf(auth) + permission_sync_to_user(auth) @is_unit_operation(['permission','app']) def app_addaccess(operation_logger, auth, apps, users=[]): @@ -1037,6 +1036,9 @@ def app_clearaccess(operation_logger, auth, apps): user_permission_clear(operation_logger, auth, app=apps, permission="main") + result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} + + return {'allowed_users': result} def app_debug(app): """ diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 4b77df70f..6b77118c5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -32,7 +32,6 @@ from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from yunohost.user import user_list, user_group_list -from yunohost.app import app_ssowatconf from yunohost.log import is_unit_operation logger = getActionLogger('yunohost.user') @@ -232,7 +231,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ else: raise MoulinetteError(169, m18n.n('permission_update_failed')) - _permission_sync_to_user(auth) + permission_sync_to_user(auth) for a in app: allowed_users = set() @@ -253,11 +252,10 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ if del_group: hook_callback('post_app_removeaccess', args=[app, disallowed_users]) - app_ssowatconf(auth) return user_permission_list(auth, app, permission) -def user_permission_clear(operation_logger, auth, app=[], permission=None): +def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_perm=True): """ Reset the permission for a specific application @@ -300,7 +298,7 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None): else: raise MoulinetteError(169, m18n.n('permission_update_failed')) - _permission_sync_to_user(auth) + permission_sync_to_user(auth) for a in app: permission_name = 'main.' + a @@ -311,12 +309,11 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None): new_user_list = ','.join(allowed_users) hook_callback('post_app_removeaccess', args=[app, new_user_list]) - app_ssowatconf(auth) return user_permission_list(auth, app, permission) @is_unit_operation(['permission','app']) -def permission_add(operation_logger, auth, app, permission, url=None, default_allow=True): +def permission_add(operation_logger, auth, app, permission, url=None, default_allow=True, sync_perm=True): """ Create a new permission for a specific application @@ -362,7 +359,8 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al operation_logger.start() if auth.add('cn=%s,ou=permission' % permission_name, attr_dict): - _permission_sync_to_user(auth) + if sync_perm: + permission_sync_to_user(auth) logger.success(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(auth, app, permission) @@ -370,7 +368,7 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al @is_unit_operation(['permission','app']) -def permission_update(operation_logger, auth, app, permission, add_url=None, remove_url=None): +def permission_update(operation_logger, auth, app, permission, add_url=None, remove_url=None, sync_perm=True): """ Update a permission for a specific application @@ -416,7 +414,8 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem operation_logger.start() if auth.update('cn=%s,ou=permission' % permission_name, {'cn':permission_name, 'URL': url}): - _permission_sync_to_user(auth) + if sync_perm: + permission_sync_to_user(auth) logger.success(m18n.n('permission_updated', permission=permission, app=app)) return user_permission_list(auth, app, permission) @@ -424,7 +423,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem @is_unit_operation(['permission','app']) -def permission_remove(operation_logger, auth, app, permission, force=False): +def permission_remove(operation_logger, auth, app, permission, force=False, sync_perm=True): """ Remove a permission for a specific application @@ -440,15 +439,17 @@ def permission_remove(operation_logger, auth, app, permission, force=False): operation_logger.start() if not auth.remove('cn=%s,ou=permission' % str(permission + '.' + app)): raise MoulinetteError(169, m18n.n('permission_deletion_failed', permission=permission, app=app)) - _permission_sync_to_user(auth) + if sync_perm: + permission_sync_to_user(auth) logger.success(m18n.n('permission_deleted', permission=permission, app=app)) -def _permission_sync_to_user(auth): +def permission_sync_to_user(auth): """ Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link """ import os + from yunohost.app import app_ssowatconf permission_attrs = [ 'cn', @@ -486,6 +487,9 @@ def _permission_sync_to_user(auth): inheritPermission = {'inheritPermission': val, 'memberUid': uid_val} if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): raise MoulinetteError(169, m18n.n('permission_update_failed')) + logger.success(m18n.n('permission_generated')) + + app_ssowatconf(auth) # Reload unscd because if not the group is not updated in the system from LDAP os.system('systemctl restart unscd') diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 32cb6f684..22b2e1b75 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -115,7 +115,6 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas """ from yunohost.domain import domain_list, _get_maindomain from yunohost.hook import hook_callback - from yunohost.app import app_ssowatconf from yunohost.utils.password import assert_password_is_strong_enough # Ensure sufficiently complex password @@ -211,13 +210,14 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas if not os.path.isdir('/home/{0}'.format(username)): logger.warning(m18n.n('user_home_creation_failed'), exc_info=1) - app_ssowatconf(auth) + + # Create group for user and add to group 'all_users' + user_group_add(auth, groupname=username, gid=uid, sync_perm=False) + user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) + user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=True) + # 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_add(auth, groupname=username, gid=uid) - user_group_update(auth, groupname=username, add_user=username, force=True) - user_group_update(auth, 'all_users', add_user=username, force=True) hook_callback('post_user_create', args=[username, mail, password, firstname, lastname]) @@ -237,7 +237,6 @@ def user_delete(operation_logger, auth, username, purge=False): purge """ - from yunohost.app import app_ssowatconf from yunohost.hook import hook_callback operation_logger.start() @@ -250,7 +249,7 @@ def user_delete(operation_logger, auth, username, purge=False): else: raise YunohostError('user_deletion_failed') - user_group_delete(auth, username, force=True) + user_group_delete(auth, username, force=True, sync_perm=True) group_list = auth.search('ou=groups,dc=yunohost,dc=org', '(&(objectclass=groupOfNamesYnh)(memberUid=%s))' @@ -263,8 +262,6 @@ def user_delete(operation_logger, auth, username, purge=False): if not auth.update('cn=%s,ou=groups' % group['cn'][0], user_list): raise YunohostError('group_update_failed') - app_ssowatconf(auth) - hook_callback('post_user_delete', args=[username, purge]) logger.success(m18n.n('user_deleted')) @@ -537,7 +534,7 @@ def user_group_list(auth, fields=None): @is_unit_operation([('groupname', 'user')]) -def user_group_add(operation_logger, auth, groupname,gid=None): +def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): """ Create group @@ -545,8 +542,7 @@ def user_group_add(operation_logger, auth, groupname,gid=None): groupname -- Must be unique """ - from yunohost.app import app_ssowatconf - from yunohost.permission import _permission_sync_to_user + from yunohost.permission import permission_sync_to_user operation_logger.start() @@ -578,16 +574,16 @@ def user_group_add(operation_logger, auth, groupname,gid=None): } if auth.add('cn=%s,ou=groups' % groupname, attr_dict): - _permission_sync_to_user(auth) - app_ssowatconf(auth) logger.success(m18n.n('group_created')) + if sync_perm: + permission_sync_to_user(auth) return {'name': groupname} raise MoulinetteError(169, m18n.n('group_creation_failed')) @is_unit_operation([('groupname', 'user')]) -def user_group_delete(operation_logger, auth, groupname, force=False): +def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm=True): """ Delete user @@ -595,8 +591,7 @@ def user_group_delete(operation_logger, auth, groupname, force=False): groupname -- Groupname to delete """ - from yunohost.app import app_ssowatconf - from yunohost.permission import _permission_sync_to_user + from yunohost.permission import permission_sync_to_user if not force and (groupname == 'all_users' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname)) @@ -605,13 +600,13 @@ def user_group_delete(operation_logger, auth, groupname, force=False): if not auth.remove('cn=%s,ou=groups' % groupname): raise MoulinetteError(169, m18n.n('group_deletion_failed')) - _permission_sync_to_user(auth) - app_ssowatconf(auth) logger.success(m18n.n('group_deleted')) + if sync_perm: + permission_sync_to_user(auth) @is_unit_operation([('groupname', 'user')]) -def user_group_update(operation_logger, auth, groupname, add_user=None, remove_user=None, force=False): +def user_group_update(operation_logger, auth, groupname, add_user=None, remove_user=None, force=False, sync_perm=True): """ Update user informations @@ -622,8 +617,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u """ - from yunohost.app import app_ssowatconf - from yunohost.permission import _permission_sync_to_user + from yunohost.permission import permission_sync_to_user attrs_to_fetch = ['member'] @@ -685,9 +679,9 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if not auth.update('cn=%s,ou=groups' % groupname, new_group_list): raise MoulinetteError(169, m18n.n('group_update_failed')) - _permission_sync_to_user(auth) logger.success(m18n.n('group_updated')) - app_ssowatconf(auth) + if sync_perm: + permission_sync_to_user(auth) return user_group_info(auth, groupname) @@ -723,24 +717,27 @@ def user_group_info(auth, groupname): # import yunohost.permission -def user_permission_list(auth, app=None, permission=None, username=None, group=None): +def user_permission_list(auth, app=None, permission=None, username=None, group=None, sync_perm=True): return yunohost.permission.user_permission_list(auth, app, permission, username, group) @is_unit_operation([('app', 'user')]) -def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None): +def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None, sync_perm=True): return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, add_username=username, add_group=group, - del_username=None, del_group=None) + del_username=None, del_group=None, + sync_perm=sync_perm) @is_unit_operation([('app', 'user')]) -def user_permission_remove(operation_logger, auth, app, permission="main", username=None, group=None): +def user_permission_remove(operation_logger, auth, app, permission="main", username=None, group=None, sync_perm=True): return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, add_username=None, add_group=None, - del_username=username, del_group=group) + del_username=username, del_group=group, + sync_perm=sync_perm) @is_unit_operation([('app', 'user')]) -def user_permission_clear(operation_logger, auth, app, permission=None): - return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission) +def user_permission_clear(operation_logger, auth, app, permission=None, sync_perm=True): + return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission, + sync_perm=sync_perm) # # SSH subcategory From 811d1f6a9c03fad10a6fda48ae70ae8cd879fd47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 8 Dec 2018 00:41:42 +0100 Subject: [PATCH 13/58] Fix app_setings_get for path --- 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 023007789..f9b7191c0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -912,7 +912,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on # Add path in permission if it's defined in the app install script app_settings = _get_app_settings(app_instance_name) domain = app_settings['domain'] - path = app_settings.get('path', '/') + path = app_settings.get('path', None) if domain and path: permission_update(auth, app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) From 0d3d33fdce4978d9b96baf282b17d697dc02a618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 14 Dec 2018 23:49:26 +0100 Subject: [PATCH 14/58] Implement migration for group and permission --- locales/en.json | 7 ++ .../0009_setup_group_permission.py | 108 ++++++++++++++++++ src/yunohost/permission.py | 5 +- 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/yunohost/data_migrations/0009_setup_group_permission.py diff --git a/locales/en.json b/locales/en.json index 3a629ea0e..23b9c5751 100644 --- a/locales/en.json +++ b/locales/en.json @@ -307,6 +307,7 @@ "migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", + "migration_description_0009_setup_group_permission": "Setup user group and setup permission for apps and services", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", @@ -332,6 +333,12 @@ "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0009_create_group": "Create group for each user.", + "migration_0009_disclaimer": "Your LDAP schema will be updated and your LDAP database will be updated", + "migration_0009_done": "Migration sucess. You are now able to use groups of user.", + "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", + "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", + "migration_0009_update_LDAP_schema": "Update LDAP schema", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py new file mode 100644 index 000000000..2b88388eb --- /dev/null +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -0,0 +1,108 @@ +import yaml +import errno + +from moulinette import m18n +from moulinette.core import MoulinetteError, init_authenticator +from moulinette.utils.log import getActionLogger + +from yunohost.tools import Migration +from yunohost.utils.filesystem import free_space_in_directory, space_used_by_directory +from yunohost.user import user_list, user_group_add, user_group_update +from yunohost.app import app_setting, app_list +from yunohost.service import service_regen_conf +from yunohost.permission import permission_add, permission_sync_to_user +from yunohost.user import user_permission_add + +logger = getActionLogger('yunohost.migration') + +################################################### +# Tools used also for restoration +################################################### + +def migrate_LDAP_db(auth): + logger.info(m18n.n("migration_0009_update_LDAP_database")) + try: + auth.remove('cn=sftpusers,ou=groups') + except Exception as e: + logger.warn("Error when trying remove sftpusers group") + + with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: + ldap_map = yaml.load(f) + + try: + attr_dict = ldap_map['parents']['ou=permission'] + auth.add('ou=permission', attr_dict) + + attr_dict = ldap_map['children']['cn=all_users,ou=groups'] + auth.add('cn=all_users,ou=groups', attr_dict) + + for rdn, attr_dict in ldap_map['depends_children'].items(): + auth.add(rdn, attr_dict) + except Exception as e: + raise MoulinetteError(errno.EINVAL, m18n.n(("LDAP_update_failled"))) + + logger.info(m18n.n("migration_0009_create_group")) + + #Create group for each yunohost user + user_list = auth.search('ou=users,dc=yunohost,dc=org', + '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', + ['uid', 'uidNumber']) + for user_info in user_list: + username = user_info['uid'][0] + user_group_add(auth, username, gid=user_info['uidNumber'][0], sync_perm=False) + user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) + user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=False) + + +def migrate_app_permission(auth): + logger.info(m18n.n("migration_0009_migrate_permission")) + + for app_info in app_list(installed=True)['apps']: + app = app_info['id'] + permission = app_setting(app, 'allowed_users') + path = app_setting(app, 'path') + domain = app_setting(app, 'domain') + + url = None + if domain and path: + url = domain + path + permission_add(auth, app, 'main', url=url, default_allow=True, sync_perm=False) + if permission: + allowed_group = permission.split(',') + user_permission_add(auth, [app], 'main', group=allowed_group, sync_perm=False) + app_setting(app, 'allowed_users', delete=True) + + +class MyMigration(Migration): + """ + Update the LDAP DB to be able to store the permission + Create a group for each yunohost user + Migrate app permission from apps setting to LDAP + """ + + required = True + + def migrate(self): + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0009_update_LDAP_schema")) + service_regen_conf(names=['slapd'], force=True) + + # Do the authentication to LDAP after LDAP as been updated + AUTH_IDENTIFIER = ('ldap', 'as-root') + AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + + #Update LDAP database + migrate_LDAP_db(auth) + + # Migrate permission + migrate_app_permission(auth) + + permission_sync_to_user(auth) + logger.info(m18n.n("migration_0009_done")) + + @property + def disclaimer(self): + return m18n.n("migration_0009_disclaimer") diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 6b77118c5..0cf77745a 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -114,7 +114,7 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N return {'permissions': permissions} -def user_permission_update(operation_logger, auth, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None): +def user_permission_update(operation_logger, auth, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application @@ -231,7 +231,8 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ else: raise MoulinetteError(169, m18n.n('permission_update_failed')) - permission_sync_to_user(auth) + if sync_perm: + permission_sync_to_user(auth) for a in app: allowed_users = set() From f22e7144b45cf2d6f32096de980e6bb0c37936e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 12 Dec 2018 17:40:29 +0100 Subject: [PATCH 15/58] Implement migration while restore backup --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/backup.py | 45 ++++++++++++++----- .../0009_setup_group_permission.py | 9 +++- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 9676baf52..8324bdb8e 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1085,7 +1085,7 @@ backup: api: POST /backup/restore/ configuration: authenticate: all - authenticator: ldap-anonymous + authenticator: as-root arguments: name: help: Name of the local backup archive diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 00e9a1e21..a2001458b 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -800,14 +800,14 @@ class RestoreManager(): Public methods: set_targets(self, system_parts=[], apps=[]) - restore(self) + restore(self, auth) Usage: restore_manager = RestoreManager(name) restore_manager.set_targets(None, ['wordpress__3']) - restore_manager.restore() + restore_manager.restore(auth) if restore_manager.success: logger.success(m18n.n('restore_complete')) @@ -893,6 +893,23 @@ class RestoreManager(): logger.debug("executing the post-install...") tools_postinstall(domain, 'yunohost', True) + def _migrate_system_if_needed(self, auth): + """ + Do some migration if needed + """ + + # Check if we need to do the migration 0009 : setup group and permission + result = auth.search('ou=groups,dc=yunohost,dc=org', + '(&(objectclass=groupOfNamesYnh)(cn=all_users))', + ['cn']) + if not result: + from importlib import import_module + migration_0009 = import_module('yunohost.data_migrations.0009_setup_group_permission') + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0009_update_LDAP_schema")) + service_regen_conf(names=['slapd'], force=True) + migration_0009.migrate_LDAP_db(auth) + def clean(self): """ End a restore operations by cleaning the working directory and @@ -1100,7 +1117,7 @@ class RestoreManager(): # "Actual restore" (reverse step of the backup collect part) # # - def restore(self): + def restore(self, auth): """ Restore the archive @@ -1114,8 +1131,9 @@ class RestoreManager(): # Apply dirty patch to redirect php5 file on php7 self._patch_backup_csv_file() - self._restore_system() - self._restore_apps() + self._restore_system(auth) + self._migrate_system_if_needed(auth) + self._restore_apps(auth) finally: self.clean() @@ -1159,7 +1177,7 @@ class RestoreManager(): logger.warning(m18n.n('backup_php5_to_php7_migration_may_fail', error=str(e))) - def _restore_system(self): + def _restore_system(self, auth): """ Restore user and system parts """ system_targets = self.targets.list("system", exclude=["Skipped"]) @@ -1199,15 +1217,15 @@ class RestoreManager(): service_regen_conf() - def _restore_apps(self): + def _restore_apps(self, auth): """Restore all apps targeted""" apps_targets = self.targets.list("apps", exclude=["Skipped"]) for app in apps_targets: - self._restore_app(app) + self._restore_app(auth, app) - def _restore_app(self, app_instance_name): + def _restore_app(self, auth, app_instance_name): """ Restore an app @@ -1286,7 +1304,12 @@ class RestoreManager(): filesystem.chown(app_scripts_new_path, 'admin', None, True) # Restore permissions - os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) + if os.path.isfile(app_restore_script_in_archive): + os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) + else: + from importlib import import_module + migration_0009 = import_module('yunohost.data_migrations.0009_setup_group_permission') + migration_0009.migrate_app_permission(auth, app=app_instance_name) # Copy the app scripts to a writable temporary folder # FIXME : use 'install -Dm555' or something similar to what's done @@ -2137,7 +2160,7 @@ def backup_restore(auth, name, system=[], apps=[], force=False): # restore_manager.mount() - restore_manager.restore() + restore_manager.restore(auth) # Check if something has been restored if restore_manager.success: diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 2b88388eb..e08f79724 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -54,10 +54,15 @@ def migrate_LDAP_db(auth): user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=False) -def migrate_app_permission(auth): +def migrate_app_permission(auth, app=None): logger.info(m18n.n("migration_0009_migrate_permission")) - for app_info in app_list(installed=True)['apps']: + if app: + apps = app_list(installed=True, filter=app)['apps'] + else: + apps = app_list(installed=True)['apps'] + + for app_info in apps: app = app_info['id'] permission = app_setting(app, 'allowed_users') path = app_setting(app, 'path') From ad3d8786d6d1fa94b5ce16e9434678890501a50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 15 Dec 2018 20:27:22 +0100 Subject: [PATCH 16/58] Change MoulinetteError to YunohostError --- src/yunohost/app.py | 2 +- .../0009_setup_group_permission.py | 6 +-- src/yunohost/permission.py | 37 +++++++++---------- src/yunohost/user.py | 26 ++++++------- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f9b7191c0..94f6680ce 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1088,7 +1088,7 @@ def app_makedefault(operation_logger, auth, app, domain=None): operation_logger.start() if '/' in app_map(auth, raw=True)[domain]: raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, - other_app=app_map(auth, raw=True)[domain]["/"]["id"])) + other_app=app_map(auth, raw=True)[domain]["/"]["id"]) try: with open('/etc/ssowat/conf.json.persistent') as json_conf: diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index e08f79724..173a96709 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -1,8 +1,8 @@ import yaml -import errno from moulinette import m18n -from moulinette.core import MoulinetteError, init_authenticator +from moulinette.core import init_authenticator +from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration @@ -39,7 +39,7 @@ def migrate_LDAP_db(auth): for rdn, attr_dict in ldap_map['depends_children'].items(): auth.add(rdn, attr_dict) except Exception as e: - raise MoulinetteError(errno.EINVAL, m18n.n(("LDAP_update_failled"))) + raise YunohostError("LDAP_update_failled") logger.info(m18n.n("migration_0009_create_group")) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 0cf77745a..b7f2b9949 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -24,13 +24,12 @@ Manage permissions """ -import errno import grp import random from moulinette import m18n -from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +from yunohost.utils.error import YunohostError from yunohost.user import user_list, user_group_list from yunohost.log import is_unit_operation @@ -163,23 +162,23 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ # Validate that the group exist for g in add_group: if not g in user_group_list(auth, ['cn'])['groups']: - raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=g)) + raise YunohostError('group_unknown', group=g) for u in add_username: if not u in user_list(auth, ['uid'])['users']: - raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=u)) + raise YunohostError('user_unknown', user=u) for g in del_group: if not g in user_group_list(auth, ['cn'])['groups']: - raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=g)) + raise YunohostError('group_unknown', group=g) for u in del_username: if not u in user_list(auth, ['uid'])['users']: - raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=u)) + 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 MoulinetteError(errno.EINVAL, m18n.n('edit_permission_with_group_all_users_not_allowed')) + raise YunohostError('edit_permission_with_group_all_users_not_allowed') # Populate permission informations permission_attrs = [ @@ -196,14 +195,14 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ for per in permission: permission_name = per + '.' + a if not permission_name in result: - raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=per, app=a)) + 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']) for g in del_group: if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: - raise MoulinetteError(errno.EINVAL, m18n.n('need_define_permission_before')) + raise YunohostError('need_define_permission_before') group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' if not group_name in new_per_dict[permission_name]: logger.warning(m18n.n('group_alread_disallowed', permission=per, app=a, group=g)) @@ -229,7 +228,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ p = per.split('.') logger.success(m18n.n('permission_updated', permission=p[0], app=p[1])) else: - raise MoulinetteError(169, m18n.n('permission_update_failed')) + raise YunohostError('permission_update_failed') if sync_perm: permission_sync_to_user(auth) @@ -290,14 +289,14 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_ for per in permission: permission_name = per + '.' + a if not permission_name in result: - raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=per, app=a)) + 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 auth.update('cn=%s,ou=permission' % permission_name, default_permission): logger.success(m18n.n('permission_updated', permission=per, app=a)) else: - raise MoulinetteError(169, m18n.n('permission_update_failed')) + raise YunohostError('permission_update_failed') permission_sync_to_user(auth) @@ -332,7 +331,7 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al 'cn': permission_name }, base_dn='ou=permission,dc=yunohost,dc=org') if conflict: - raise MoulinetteError(errno.EEXIST, m18n.n('permission_already_exist', permission=permission, app=app)) + raise YunohostError('permission_already_exist', permission=permission, app=app) # Get random GID all_gid = {x.gr_gid for x in grp.getgrall()} @@ -365,7 +364,7 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al logger.success(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise MoulinetteError(169, m18n.n('premission_creation_failled')) + raise YunohostError('premission_creation_failled') @is_unit_operation(['permission','app']) @@ -388,7 +387,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem result = auth.search(base='ou=permission,dc=yunohost,dc=org', filter='cn=' + permission_name, attrs=['URL']) if not result: - raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=permission, app=app)) + raise YunohostError('permission_not_found', permission=permission, app=app) permission_obj = result[0] if not 'URL' in permission_obj: @@ -420,7 +419,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem logger.success(m18n.n('permission_updated', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise MoulinetteError(169, m18n.n('premission_update_failled')) + raise YunohostError('premission_update_failled') @is_unit_operation(['permission','app']) @@ -435,11 +434,11 @@ def permission_remove(operation_logger, auth, app, permission, force=False, sync """ if permission == "main" and not force: - raise MoulinetteError(errno.EPERM, m18n.n('remove_main_permission_not_allowed')) + raise YunohostError('remove_main_permission_not_allowed') operation_logger.start() if not auth.remove('cn=%s,ou=permission' % str(permission + '.' + app)): - raise MoulinetteError(169, m18n.n('permission_deletion_failed', permission=permission, app=app)) + raise YunohostError('permission_deletion_failed', permission=permission, app=app) if sync_perm: permission_sync_to_user(auth) logger.success(m18n.n('permission_deleted', permission=permission, app=app)) @@ -487,7 +486,7 @@ def permission_sync_to_user(auth): uid_val = [v.split("=")[1].split(",")[0] for v in val] inheritPermission = {'inheritPermission': val, 'memberUid': uid_val} if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): - raise MoulinetteError(169, m18n.n('permission_update_failed')) + raise YunohostError('permission_update_failed') logger.success(m18n.n('permission_generated')) app_ssowatconf(auth) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 22b2e1b75..b800c9b0e 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -496,8 +496,7 @@ def user_group_list(auth, fields=None): if attr in keys: attrs.append(attr) else: - raise MoulinetteError(errno.EINVAL, - m18n.n('field_invalid', attr)) + raise YunohostError('field_invalid', attr) else: attrs = ['cn', 'member'] @@ -551,12 +550,12 @@ def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): 'cn': groupname }, base_dn='ou=groups,dc=yunohost,dc=org') if conflict: - raise MoulinetteError(errno.EEXIST, m18n.n('group_name_already_exist', name=groupname)) + raise YunohostError('group_name_already_exist', name=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 MoulinetteError(errno.EEXIST, m18n.n('system_groupname_exists')) + raise YunohostError('system_groupname_exists') if not gid: # Get random GID @@ -579,7 +578,7 @@ def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): permission_sync_to_user(auth) return {'name': groupname} - raise MoulinetteError(169, m18n.n('group_creation_failed')) + raise YunohostError('group_creation_failed') @is_unit_operation([('groupname', 'user')]) @@ -594,11 +593,11 @@ def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm= from yunohost.permission import permission_sync_to_user if not force and (groupname == 'all_users' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): - raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname)) + raise YunohostError('group_deletion_not_allowed', user=groupname) operation_logger.start() if not auth.remove('cn=%s,ou=groups' % groupname): - raise MoulinetteError(169, m18n.n('group_deletion_failed')) + raise YunohostError('group_deletion_failed') logger.success(m18n.n('group_deleted')) if sync_perm: @@ -622,13 +621,13 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u attrs_to_fetch = ['member'] if (groupname == 'all_users' or groupname == 'admins') and not force: - raise MoulinetteError(errno.EINVAL, m18n.n('edit_group_not_allowed', group=groupname)) + raise YunohostError('edit_group_not_allowed', group=groupname) # Populate group informations result = auth.search(base='ou=groups,dc=yunohost,dc=org', filter='cn=' + groupname, attrs=attrs_to_fetch) if not result: - raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname)) + raise YunohostError('group_unknown', group=groupname) group = result[0] new_group_list = {'member': set(), 'memberUid': set()} @@ -644,7 +643,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u add_user = [add_user] for user in add_user: if not user in user_l: - raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=user)) + raise YunohostError('user_unknown', user=user) userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if userDN in group['member']: logger.warning(m18n.n('user_alread_in_group', user=user, group=groupname)) @@ -656,8 +655,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u for user in remove_user: userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if user == groupname: - raise MoulinetteError(errno.EINVAL, - m18n.n('remove_user_of_group_not_allowed', user=user, group=groupname)) + raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname) if 'member' in group and userDN in group['member']: new_group_list['member'].remove(userDN) else: @@ -677,7 +675,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if new_group_list['member'] != set(group['member']): if not auth.update('cn=%s,ou=groups' % groupname, new_group_list): - raise MoulinetteError(169, m18n.n('group_update_failed')) + raise YunohostError('group_update_failed') logger.success(m18n.n('group_updated')) if sync_perm: @@ -699,7 +697,7 @@ def user_group_info(auth, groupname): result = auth.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs) if not result: - raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname)) + raise YunohostError('group_unknown', group=groupname) else: group = result[0] From 1de4625d01302eed189da2ea4fd19e94bed65b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 28 Dec 2018 10:12:08 +0100 Subject: [PATCH 17/58] Remove disclaimer for migration --- locales/en.json | 1 - src/yunohost/data_migrations/0009_setup_group_permission.py | 4 ---- 2 files changed, 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 23b9c5751..3010edf17 100644 --- a/locales/en.json +++ b/locales/en.json @@ -334,7 +334,6 @@ "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0009_create_group": "Create group for each user.", - "migration_0009_disclaimer": "Your LDAP schema will be updated and your LDAP database will be updated", "migration_0009_done": "Migration sucess. You are now able to use groups of user.", "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 173a96709..5767e20d5 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -107,7 +107,3 @@ class MyMigration(Migration): permission_sync_to_user(auth) logger.info(m18n.n("migration_0009_done")) - - @property - def disclaimer(self): - return m18n.n("migration_0009_disclaimer") From 6d3cb916b5cfdf47f3d8e1a7c5fe519c79569bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 28 Dec 2018 10:23:22 +0100 Subject: [PATCH 18/58] Improve migration --- locales/en.json | 7 +++ .../0009_setup_group_permission.py | 51 +++++++++++++++---- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3010edf17..45c1fbb90 100644 --- a/locales/en.json +++ b/locales/en.json @@ -183,6 +183,7 @@ "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}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", @@ -333,9 +334,15 @@ "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0009_backup_before_migration": "Make a backup of LDAP and apps settings before the migration", + "migration_0009_can_not_backup_before_migration": "The backup of the system before the migration failled. Migration failed. Error : {error:s}", "migration_0009_create_group": "Create group for each user.", "migration_0009_done": "Migration sucess. You are now able to use groups of user.", + "migration_0009_failled": "Migration failed.", + "migration_0009_LDAP_update_failled": "LDAP update failled. Error : {error:s}", "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", + "migration_0009_migration_failled_try_rollback": "Migration failed. Try to restore the system. Error : {error:s}", + "migration_0009_rollback_success": "System restored.", "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", "migration_0009_update_LDAP_schema": "Update LDAP schema", "migrations_backward": "Migrating backward.", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 5767e20d5..2dee0b070 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -1,4 +1,7 @@ import yaml +import time +import os +import shutil from moulinette import m18n from moulinette.core import init_authenticator @@ -23,8 +26,8 @@ def migrate_LDAP_db(auth): logger.info(m18n.n("migration_0009_update_LDAP_database")) try: auth.remove('cn=sftpusers,ou=groups') - except Exception as e: - logger.warn("Error when trying remove sftpusers group") + except: + logger.warn(m18n.n("error_when_removing_sftpuser_group")) with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: ldap_map = yaml.load(f) @@ -39,16 +42,18 @@ def migrate_LDAP_db(auth): for rdn, attr_dict in ldap_map['depends_children'].items(): auth.add(rdn, attr_dict) except Exception as e: - raise YunohostError("LDAP_update_failled") + raise YunohostError("migration_0009_LDAP_update_failled", error=e) logger.info(m18n.n("migration_0009_create_group")) - #Create group for each yunohost user + #Create a group for each yunohost user user_list = auth.search('ou=users,dc=yunohost,dc=org', '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', ['uid', 'uidNumber']) for user_info in user_list: username = user_info['uid'][0] + auth.update('uid=%s,ou=users' % username, + {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) user_group_add(auth, username, gid=user_info['uidNumber'][0], sync_perm=False) user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=False) @@ -88,6 +93,19 @@ class MyMigration(Migration): required = True def migrate(self): + # Backup LDAP and the apps settings before to do the migration + logger.info(m18n.n("migration_0009_backup_before_migration")) + try: + backup_folder = "/home/yunohost.backup/premigration/" + time.strftime('%Y%m%d-%H%M%S', time.gmtime()) + os.makedirs(backup_folder, 0o750) + os.system("systemctl stop slapd") + os.system("cp -r --preserve /etc/ldap %s/ldap_config" % backup_folder) + os.system("cp -r --preserve /var/lib/ldap %s/ldap_db" % backup_folder) + os.system("cp -r --preserve /etc/yunohost/apps %s/apps_settings" % backup_folder) + os.system("systemctl start slapd") + except Exception as e: + raise YunohostError("migration_0009_can_not_backup_before_migration", error=e) + # Update LDAP schema restart slapd logger.info(m18n.n("migration_0009_update_LDAP_schema")) service_regen_conf(names=['slapd'], force=True) @@ -99,11 +117,26 @@ class MyMigration(Migration): 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) - #Update LDAP database - migrate_LDAP_db(auth) + try: + #Update LDAP database + migrate_LDAP_db(auth) - # Migrate permission - migrate_app_permission(auth) + # Migrate permission + migrate_app_permission(auth) + + permission_sync_to_user(auth) + except Exception as e: + logger.warn(m18n.n("migration_0009_migration_failled_try_rollback", error=e)) + os.system("systemctl stop slapd") + os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config + os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) + os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder) + os.system("cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" % backup_folder) + os.system("systemctl start slapd") + os.system("rm -r " + backup_folder) + logger.info(m18n.n("migration_0009_rollback_success")) + raise YunohostError("migration_0009_failled") + + os.system("rm -r " + backup_folder) - permission_sync_to_user(auth) logger.info(m18n.n("migration_0009_done")) From efffff750e47f842bbddfdd4a997cfc4aab2040e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 28 Dec 2018 15:37:24 +0100 Subject: [PATCH 19/58] Implement Tests for groups and permissions --- src/yunohost/tests/test_backuprestore.py | 7 + src/yunohost/tests/test_permission.py | 316 +++++++++++++++++++++++ src/yunohost/tests/test_user-group.py | 210 +++++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 src/yunohost/tests/test_permission.py create mode 100644 src/yunohost/tests/test_user-group.py diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 5775e1612..3560181bc 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -13,6 +13,7 @@ from yunohost.app import _is_installed from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError +from yunohost.user import user_permission_list # Get main domain maindomain = "" @@ -535,6 +536,7 @@ def _test_backup_and_restore_app(app): # Uninstall the app app_remove(auth, app) assert not app_is_installed(app) + assert app not in user_permission_list(auth)['permissions'] # Restore the app backup_restore(auth, system=None, name=archives[0], @@ -542,6 +544,11 @@ def _test_backup_and_restore_app(app): assert app_is_installed(app) + # Check permission + per_list = user_permission_list(auth)['permissions'] + assert app in per_list + assert "main" in per_list[app] + # # Some edge cases # # diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py new file mode 100644 index 000000000..c6b7c4129 --- /dev/null +++ b/src/yunohost/tests/test_permission.py @@ -0,0 +1,316 @@ +import pytest + +from moulinette.core import init_authenticator, MoulinetteError +from yunohost.app import app_install, app_remove, app_change_url +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.domain import _get_maindomain +from yunohost.utils.error import YunohostError + +# Get main domain +maindomain = _get_maindomain() + +# Instantiate LDAP Authenticator +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + +auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + +def clean_user_groups_permission(): + for u in user_list(auth)['users']: + user_delete(auth, u) + + for g in user_group_list(auth)['groups']: + if g != "all_users": + user_group_delete(auth, g) + + for a, per in user_permission_list(auth)['permissions'].items(): + if a in ['wiki', 'blog', 'site']: + for p in per: + permission_remove(auth, a, p, force=True, sync_perm=False) + +def setup_function(function): + clean_user_groups_permission() + + user_create(auth, "alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") + user_create(auth, "bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") + permission_add(auth, "wiki", "main", [maindomain + "/wiki"], sync_perm=False) + permission_add(auth, "blog", "main", sync_perm=False) + + user_permission_add(auth, ["blog"], "main", group="alice") + +def teardown_function(function): + clean_user_groups_permission() + try: + app_remove(auth, "permissions_app") + except: + pass + +# +# List functions +# + +def test_list_permission(): + res = user_permission_list(auth)['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 "metronome" in res + assert "main" in res['metronome'] + 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'] + +# +# Create - Remove functions +# + +def test_add_permission_1(): + permission_add(auth, "site", "test") + + res = user_permission_list(auth)['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_add(auth, "site", "main", default_allow=False) + + res = user_permission_list(auth)['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_remove(auth, "wiki", "main", force=True) + + res = user_permission_list(auth)['permissions'] + assert "wiki" not in res + +# +# Error on create - remove function +# + +def test_add_bad_permission(): + # Create permission with same name + with pytest.raises(YunohostError): + permission_add(auth, "wiki", "main") + +def test_remove_bad_permission(): + # Remove not existant permission + with pytest.raises(MoulinetteError): + permission_remove(auth, "non_exit", "main", force=True) + + res = user_permission_list(auth)['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 "metronome" in res + assert "main" in res['metronome'] + +def test_remove_main_permission(): + with pytest.raises(YunohostError): + permission_remove(auth, "blog", "main") + + res = user_permission_list(auth)['permissions'] + assert "mail" in res + assert "main" in res['mail'] + +# +# Update functions +# + +# user side functions + +def test_allow_first_group(): + # Remove permission to all_users and define per users + user_permission_add(auth, ["wiki"], "main", group="alice") + + res = user_permission_list(auth)['permissions'] + assert ['alice'] == res['wiki']['main']['allowed_users'] + assert ['alice'] == res['wiki']['main']['allowed_groups'] + +def test_allow_other_group(): + # Allow new user in a permission + user_permission_add(auth, ["blog"], "main", group="bob") + + res = user_permission_list(auth)['permissions'] + assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) + assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_groups']) + +def test_disallow_group_1(): + # Disallow a user in a permission + user_permission_remove(auth, ["blog"], "main", group="alice") + + res = user_permission_list(auth)['permissions'] + assert [] == res['blog']['main']['allowed_users'] + assert [] == res['blog']['main']['allowed_groups'] + +def test_allow_group_1(): + # Allow a user when he is already allowed + user_permission_add(auth, ["blog"], "main", group="alice") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_users'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + +def test_disallow_group_1(): + # Disallow a user when he is already disallowed + user_permission_remove(auth, ["blog"], "main", group="bob") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_users'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + +def test_reset_permission(): + # Reset permission + user_permission_remove(auth, ["blog"], "main", group="bob") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_users'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + +# internal functions + +def test_add_url_1(): + # Add URL in permission which hasn't any URL defined + permission_update(auth, "blog", "main", add_url=[maindomain + "/testA"]) + + res = user_permission_list(auth)['permissions'] + assert [maindomain + "/testA"] == res['blog']['main']['URL'] + +def test_add_url_2(): + # Add a second URL in a permission + permission_update(auth, "wiki", "main", add_url=[maindomain + "/testA"]) + + res = user_permission_list(auth)['permissions'] + assert set([maindomain + "/testA", maindomain + "/wiki"]) == set(res['wiki']['main']['URL']) + +def test_remove_url_1(): + permission_update(auth, "wiki", "main", remove_url=[maindomain + "/wiki"]) + + res = user_permission_list(auth)['permissions'] + assert 'URL' not in res['wiki']['main'] + +def test_add_url_3(): + # Add a url already added + permission_update(auth, "wiki", "main", add_url=[maindomain + "/wiki"]) + + res = user_permission_list(auth)['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(auth, "wiki", "main", remove_url=[maindomain + "/not_exist"]) + + res = user_permission_list(auth)['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(auth, "blog", "main", remove_url=[maindomain + "/not_exist"]) + + res = user_permission_list(auth)['permissions'] + assert 'URL' not in res['blog']['main'] + +# +# Error on update function +# + +def test_disallow_bad_group_1(): + # Disallow a group when the group all_users is allowed + with pytest.raises(YunohostError): + user_permission_remove(auth, "wiki", "main", group="alice") + + res = user_permission_list(auth)['permissions'] + assert ["all_users"] == res['wiki']['main']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) + +def test_allow_bad_user(): + # Allow a non existant group + with pytest.raises(YunohostError): + user_permission_add(auth, ["blog"], "main", group="not_exist") + + res = user_permission_list(auth)['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(auth, ["blog"], "main", group="not_exist") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + assert ["alice"] == res['blog']['main']['allowed_users'] + +def test_allow_bad_permission_1(): + # Allow a user to a non existant permission + with pytest.raises(YunohostError): + user_permission_add(auth, ["wiki"], "not_exit", group="alice") + +def test_allow_bad_permission_2(): + # Allow a user to a non existant permission + with pytest.raises(YunohostError): + user_permission_add(auth, ["not_exit"], "main", group="alice") + +# +# Application interaction +# + +def test_install_app(): + app_install(auth, "./tests/apps/permissions_app_ynh", + args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + + res = user_permission_list(auth)['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'] + + assert ["all_users"] == res['permissions_app']['main']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['permissions_app']['main']['allowed_users']) + + assert ["alice"] == res['permissions_app']['admin']['allowed_groups'] + assert ["alice"] == res['permissions_app']['admin']['allowed_users'] + + assert ["all_users"] == res['permissions_app']['dev']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['permissions_app']['dev']['allowed_users']) + +def test_remove_app(): + app_install(auth, "./tests/apps/permissions_app_ynh", + args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + app_remove(auth, "permissions_app") + + res = user_permission_list(auth)['permissions'] + assert "permissions_app" not in res + +def test_change_url(): + app_install(auth, "./tests/apps/permissions_app_ynh", + args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + + res = user_permission_list(auth)['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'] + + app_change_url(auth, "permissions_app", maindomain, "/newchangeurl") + + res = user_permission_list(auth)['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'] diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py new file mode 100644 index 000000000..351a3b432 --- /dev/null +++ b/src/yunohost/tests/test_user-group.py @@ -0,0 +1,210 @@ +import pytest + +from moulinette.core import init_authenticator, 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.domain import _get_maindomain +from yunohost.utils.error import YunohostError + +# Get main domain +maindomain = _get_maindomain() + +# Instantiate LDAP Authenticator +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + +auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + +def clean_user_groups(): + for u in user_list(auth)['users']: + user_delete(auth, u) + + for g in user_group_list(auth)['groups']: + if g != "all_users": + user_group_delete(auth, g) + +def setup_function(function): + clean_user_groups() + + user_create(auth, "alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") + user_create(auth, "bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") + user_create(auth, "jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh") + + user_group_add(auth, "dev") + user_group_add(auth, "apps") + user_group_update(auth, "dev", add_user=["alice"]) + user_group_update(auth, "apps", add_user=["bob"]) + +def teardown_function(function): + clean_user_groups() + +# +# List functions +# + +def test_list_users(): + res = user_list(auth)['users'] + + assert "alice" in res + assert "bob" in res + assert "jack" in res + +def test_list_groups(): + res = user_group_list(auth)['groups'] + + assert "all_users" in res + assert "alice" in res + assert "bob" in res + assert "jack" in res + for u in ["alice", "bob", "jack"]: + assert u in res + assert u in res[u]['members'] + assert u in res["all_users"]['members'] + +# +# Create - Remove functions +# + +def test_create_user(): + user_create(auth, "albert", "Albert", "Good", "alber@" + maindomain, "test123Ynh") + + group_res = user_group_list(auth)['groups'] + assert "albert" in user_list(auth)['users'] + assert "albert" in group_res + assert "albert" in group_res['albert']['members'] + assert "albert" in group_res['all_users']['members'] + +def test_del_user(): + user_delete(auth, "alice") + + group_res = user_group_list(auth)['groups'] + assert "alice" not in user_list(auth) + assert "alice" not in group_res + assert "alice" not in group_res['all_users']['members'] + +def test_add_group(): + user_group_add(auth, "adminsys") + + group_res = user_group_list(auth)['groups'] + assert "adminsys" in group_res + assert "members" not in group_res['adminsys'] + +def test_del_group(): + user_group_delete(auth, "dev") + + group_res = user_group_list(auth)['groups'] + assert "dev" not in group_res + +# +# Error on create / remove function +# + +def test_add_bad_user_1(): + # Check email already exist + with pytest.raises(MoulinetteError): + user_create(auth, "alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh") + +def test_add_bad_user_2(): + # Check to short password + with pytest.raises(MoulinetteError): + user_create(auth, "other", "Alice", "White", "other@" + maindomain, "12") + +def test_add_bad_user_3(): + # Check user already exist + with pytest.raises(MoulinetteError): + user_create(auth, "alice", "Alice", "White", "other@" + maindomain, "test123Ynh") + +def test_del_bad_user_1(): + # Check user not found + with pytest.raises(MoulinetteError): + user_delete(auth, "not_exit") + +def test_add_bad_group_1(): + # Check groups already exist with special group "all_users" + with pytest.raises(YunohostError): + user_group_add(auth, "all_users") + +def test_add_bad_group_2(): + # Check groups already exist (for standard groups) + with pytest.raises(MoulinetteError): + user_group_add(auth, "dev") + +def test_del_bad_group_1(): + # Check not allowed to remove this groups + with pytest.raises(YunohostError): + user_group_delete(auth, "all_users") + +def test_del_bad_group_2(): + # Check groups not found + with pytest.raises(MoulinetteError): + user_group_delete(auth, "not_exit") + +# +# Update function +# + +def test_update_user_1(): + user_update(auth, "alice", firstname="NewName", lastname="NewLast") + + info = user_info(auth, "alice") + assert "NewName" == info['firstname'] + assert "NewLast" == info['lastname'] + +def test_update_group_1(): + user_group_update(auth, "dev", add_user=["bob"]) + + group_res = user_group_list(auth)['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(auth, "apps", add_user=["bob"]) + + group_res = user_group_list(auth)['groups'] + assert ["bob"] == group_res['apps']['members'] + +def test_update_group_3(): + # Try to remove a user in a group + user_group_update(auth, "apps", remove_user=["bob"]) + + group_res = user_group_list(auth)['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(auth, "apps", remove_user=["jack"]) + + group_res = user_group_list(auth)['groups'] + assert ["bob"] == group_res['apps']['members'] + +# +# Error on update functions +# + +def test_bad_update_user_1(): + # Check user not found + with pytest.raises(YunohostError): + user_update(auth, "not_exit", firstname="NewName", lastname="NewLast") + +def bad_update_group_1(): + # Check groups not found + with pytest.raises(YunohostError): + user_group_update(auth, "not_exit", add_user=["alice"]) + +def test_bad_update_group_2(): + # Check remove user in groups "all_users" not allowed + with pytest.raises(YunohostError): + user_group_update(auth, "all_users", remove_user=["alice"]) + +def test_bad_update_group_3(): + # Check remove user in it own group not allowed + with pytest.raises(YunohostError): + user_group_update(auth, "alice", remove_user=["alice"]) + +def test_bad_update_group_1(): + # Check add bad user in group + with pytest.raises(YunohostError): + user_group_update(auth, "dev", add_user=["not_exist"]) + + assert "not_exist" not in user_group_list(auth)["groups"]["dev"] From 5a6a85ab077dad588c55ad37b920ae0171514a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 12:04:05 +0100 Subject: [PATCH 20/58] Add check LDAP integrity --- src/yunohost/tests/test_backuprestore.py | 7 ++ src/yunohost/tests/test_permission.py | 94 +++++++++++++++++++++++- src/yunohost/tests/test_user-group.py | 8 ++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 3560181bc..508ea890b 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -14,6 +14,7 @@ from yunohost.backup import backup_create, backup_restore, backup_list, backup_i from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError from yunohost.user import user_permission_list +from yunohost.tests.test_permission import check_LDAP_db_integrity # Get main domain maindomain = "" @@ -91,6 +92,12 @@ def teardown_function(function): shutil.rmtree("/opt/test_backup_output_directory") +@pytest.fixture(autouse=True) +def check_LDAP_db_integrity_call(): + check_LDAP_db_integrity() + yield + check_LDAP_db_integrity() + # # Helpers # # diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index c6b7c4129..2ed84f937 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -48,6 +48,94 @@ def teardown_function(function): except: pass +@pytest.fixture(autouse=True) +def check_LDAP_db_integrity_call(): + check_LDAP_db_integrity() + yield + check_LDAP_db_integrity() + +def check_LDAP_db_integrity(): + # Here we check that all attributes in all object are sychronized. + # Here is the list of attributes per object: + # user : memberOf, permission + # group : member, permission + # permission : groupPermission, inheritPermission + # + # The idea is to check that all attributes on all sides of object are sychronized. + # 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 + + user_search = auth.search('ou=users,dc=yunohost,dc=org', + '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', + ['uid', 'memberOf', 'permission']) + group_search = auth.search('ou=groups,dc=yunohost,dc=org', + '(objectclass=groupOfNamesYnh)', + ['cn', 'member', 'memberUid', 'permission']) + permission_search = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', + ['cn', 'groupPermission', 'inheritPermission', 'memberUid']) + + user_map = {u['uid'][0]: u for u in user_search} + group_map = {g['cn'][0]: g for g in group_search} + permission_map = {p['cn'][0]: p for p in permission_search} + + 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']] + + for group in group_list: + assert user_dn in group_map[group]['member'] + 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']] + + for user in user_list: + assert permission_dn in user_map[user]['permission'] + 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) + + 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']] + + for user in user_list: + assert group_dn in user_map[user]['memberOf'] + 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) + # # List functions # @@ -177,11 +265,11 @@ def test_disallow_group_1(): def test_reset_permission(): # Reset permission - user_permission_remove(auth, ["blog"], "main", group="bob") + user_permission_clear(auth, ["blog"], "main") res = user_permission_list(auth)['permissions'] - assert ["alice"] == res['blog']['main']['allowed_users'] - assert ["alice"] == res['blog']['main']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) + assert ["all_users"] == res['blog']['main']['allowed_groups'] # internal functions diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 351a3b432..48b56884e 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -4,6 +4,7 @@ from moulinette.core import init_authenticator, 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.domain import _get_maindomain from yunohost.utils.error import YunohostError +from yunohost.tests.test_permission import check_LDAP_db_integrity # Get main domain maindomain = _get_maindomain() @@ -39,6 +40,12 @@ def setup_function(function): def teardown_function(function): clean_user_groups() +@pytest.fixture(autouse=True) +def check_LDAP_db_integrity_call(): + check_LDAP_db_integrity() + yield + check_LDAP_db_integrity() + # # List functions # @@ -187,6 +194,7 @@ def test_bad_update_user_1(): with pytest.raises(YunohostError): user_update(auth, "not_exit", firstname="NewName", lastname="NewLast") + def bad_update_group_1(): # Check groups not found with pytest.raises(YunohostError): From 9109850fb06b88c07bf3c493f52d7aad9487a81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 22:56:41 +0100 Subject: [PATCH 21/58] Fix mailbox info when user not allowed to access to mail --- locales/en.json | 1 + src/yunohost/user.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/locales/en.json b/locales/en.json index 45c1fbb90..6ab780feb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -288,6 +288,7 @@ "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", + "mailbox_disabled": "Mailbox disabled for user {user:s}", "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", "mail_unavailable": "This email address is reserved and shall be automatically allocated to the very first user", "maindomain_change_failed": "Unable to change the main domain", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index b800c9b0e..5e58909f7 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -437,6 +437,8 @@ def user_info(auth, username): if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) + elif not user_permission_list(auth, app="mail", permission="main", username=username)['permissions']: + logger.warning(m18n.n('mailbox_disabled', user=username)) else: cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] cmd_result = subprocess.check_output(cmd, stderr=subprocess.STDOUT, From 146c4093b4d9c96606f3d234e5ec01603db24d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 22:58:49 +0100 Subject: [PATCH 22/58] Fix Typo Permission --- locales/en.json | 2 +- src/yunohost/permission.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6ab780feb..5ddf14590 100644 --- a/locales/en.json +++ b/locales/en.json @@ -411,7 +411,7 @@ "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", - "premission_creation_failled": "Permission creation failed", + "permission_creation_failled": "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}", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index b7f2b9949..f4bd771a5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -364,7 +364,7 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al logger.success(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise YunohostError('premission_creation_failled') + raise YunohostError('permission_creation_failled') @is_unit_operation(['permission','app']) From f999f85507ae94fb27aa5d882401a5ce213f352e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 22:59:51 +0100 Subject: [PATCH 23/58] Sync permission at the end of backup --- src/yunohost/backup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index a2001458b..c22182a45 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -910,11 +910,12 @@ class RestoreManager(): service_regen_conf(names=['slapd'], force=True) migration_0009.migrate_LDAP_db(auth) - def clean(self): + def clean(self, auth): """ End a restore operations by cleaning the working directory and regenerate ssowat conf (if some apps were restored) """ + from permission import permission_sync_to_user successfull_apps = self.targets.list("apps", include=["Success", "Warning"]) @@ -923,6 +924,8 @@ class RestoreManager(): # ldap restore hooks os.system('sudo yunohost app ssowatconf') + permission_sync_to_user(auth, force=True) + if os.path.ismount(self.work_dir): ret = subprocess.call(["umount", self.work_dir]) if ret != 0: @@ -1135,7 +1138,7 @@ class RestoreManager(): self._migrate_system_if_needed(auth) self._restore_apps(auth) finally: - self.clean() + self.clean(auth) def _patch_backup_csv_file(self): """ From e938c6d309d9e6d109ff320496aa77cfab7db640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 23:02:49 +0100 Subject: [PATCH 24/58] Improve sync permission function --- src/yunohost/permission.py | 71 +++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index f4bd771a5..77f9adbfe 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -444,49 +444,64 @@ def permission_remove(operation_logger, auth, app, permission, force=False, sync logger.success(m18n.n('permission_deleted', permission=permission, app=app)) -def permission_sync_to_user(auth): +def permission_sync_to_user(auth, force=False): """ 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 wich use "slapadd" which don't 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 permission_attrs = [ 'cn', 'member', - 'permission', ] group_info = auth.search('ou=groups,dc=yunohost,dc=org', '(objectclass=groupOfNamesYnh)', permission_attrs) - user_permission={} - - for group in group_info: - if 'permission' not in group: - continue - if not 'member' in group: - continue - for permission in group['permission']: - permission = permission.split("=")[1].split(",")[0] - if not permission in user_permission: - user_permission[permission] = set() - for member in group['member']: - user_permission[permission].add(member) + group_info = {g['cn'][0]: g for g in group_info} for per in auth.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', ['cn', 'inheritPermission']): - if per['cn'][0] in user_permission: - val = set(user_permission[per['cn'][0]]) - else: - # If the new value and the old value à empty nothing to do - if not 'inheritPermission' in per: - continue - val = set() - if 'inheritPermission' in per and val == set(per['inheritPermission']): + '(objectclass=permissionYnh)', + ['cn', 'inheritPermission', 'groupPermission', 'memberUid']): + if 'groupPermission' not in per: continue - uid_val = [v.split("=")[1].split(",")[0] for v in val] - inheritPermission = {'inheritPermission': val, 'memberUid': uid_val} - if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): - raise YunohostError('permission_update_failed') + 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) + + if 'inheritPermission' not in per: + per['inheritPermission'] = [] + if 'memberUid' not in per: + per['memberUid'] = [] + + 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: + continue + inheritPermission = {'inheritPermission': user_permission, 'memberUid': uid_val} + if force: + if per['groupPermission']: + if not auth.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': []}): + raise YunohostError('permission_update_failed_clear') + if not auth.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': per['groupPermission']}): + raise YunohostError('permission_update_failed_populate') + if per['inheritPermission']: + if not auth.update('cn=%s,ou=permission' % per['cn'][0], {'inheritPermission': []}): + raise YunohostError('permission_update_failed_clear') + if user_permission: + if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): + raise YunohostError('permission_update_failed') + else: + if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): + raise YunohostError('permission_update_failed') logger.success(m18n.n('permission_generated')) app_ssowatconf(auth) From e199b03d7d2165af814be1df5b0594efbd625d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 11 Jan 2019 17:22:03 +0100 Subject: [PATCH 25/58] Use nscd command to invalide group informations --- src/yunohost/permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 77f9adbfe..4be463e50 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -507,4 +507,5 @@ def permission_sync_to_user(auth, force=False): app_ssowatconf(auth) # Reload unscd because if not the group is not updated in the system from LDAP - os.system('systemctl restart unscd') + os.system('nscd --invalidate=passwd') + os.system('nscd --invalidate=group') From 6054e4eb3cf29c7c0b0fa87c88aa63eed63e0995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 16 Jan 2019 00:00:54 +0100 Subject: [PATCH 26/58] Improve permission list --- src/yunohost/permission.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 4be463e50..421f1ff2f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -61,13 +61,10 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N 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] - if isinstance(username, list): - group.extend(username) - else: - group.append(username) - group = filter(None, group) permissions = {} @@ -94,7 +91,9 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N continue if permission and not permission_name in permission: continue - if group and not set(group) & set(group_name): + if username[0] and not set(username) & set(user_name): + continue + if group[0] and not set(group) & set(group_name): continue if not app_name in permissions: From 022d3922c1cc68da968755305086566079c73836 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Jan 2019 20:44:45 +0000 Subject: [PATCH 27/58] This is a list of url so should be called 'urls' --- data/helpers.d/setting | 14 +++++++------- .../data_migrations/0009_setup_group_permission.py | 2 +- src/yunohost/permission.py | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 585c7ccd0..852fbf993 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -28,26 +28,26 @@ ynh_app_setting_delete() { # Create a new permission for the app # -# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--url "url" ["url" ...]] +# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--urls "url" ["url" ...]] # | arg: app - the application id # | 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: url - the url for the the permission +# | arg: urls - the list of urls for the the permission ynh_permission_create() { - declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=url= ) + declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=urls= ) local app local permission local defaultdisallow - local url + local urls ynh_handle_getopts_args "$@" if [[ -n ${defaultdisallow:-} ]]; then defaultdisallow=",default_allow=False" fi - if [[ -n ${url:-} ]]; then - url=",url=['${url//';'/"','"}']" + if [[ -n ${urls:-} ]]; then + urls=",urls=['${urls//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_add; permission_add(auth, '$app', '$permission' ${defaultdisallow:-} ${url:-}, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_add; permission_add(auth, '$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) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 2dee0b070..2d3ef5905 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -76,7 +76,7 @@ def migrate_app_permission(auth, app=None): url = None if domain and path: url = domain + path - permission_add(auth, app, 'main', url=url, default_allow=True, sync_perm=False) + permission_add(auth, app, 'main', urls=[url], default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_add(auth, [app], 'main', group=allowed_group, sync_perm=False) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 421f1ff2f..e490c33bd 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -312,14 +312,14 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_ @is_unit_operation(['permission','app']) -def permission_add(operation_logger, auth, app, permission, url=None, default_allow=True, sync_perm=True): +def permission_add(operation_logger, auth, app, permission, urls=None, default_allow=True, 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) - url -- list of url to specify for the permission + urls -- list of urls to specify for the permission """ from yunohost.domain import _normalize_domain_path @@ -348,11 +348,11 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al if default_allow: attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' - if url: + if urls: attr_dict['URL'] = [] - for u in url: - domain = u[:u.index('/')] - path = u[u.index('/'):] + 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) From 3a6f7cbc976998624f7a73db9425b712bcf4f6d4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Jan 2019 21:07:06 +0000 Subject: [PATCH 28/58] Typo / wording --- locales/en.json | 28 +++++++++---------- .../0009_setup_group_permission.py | 6 ++-- src/yunohost/permission.py | 8 +++--- src/yunohost/user.py | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5ddf14590..c58692f01 100644 --- a/locales/en.json +++ b/locales/en.json @@ -211,8 +211,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_alread_allowed": "Group '{group:s}' already allowed to access to permission '{permission:s}' for app '{app:s}'", - "group_alread_disallowed": "Group '{group:s}' already disallowed to access to permission '{permission:s}' for app '{app:s}'", + "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_name_already_exist": "Group {name:s} already exist", "group_created": "Group creation success", "group_creation_failed": "Group creation failed", @@ -220,9 +220,9 @@ "group_deletion_failed": "Group deletion failed", "group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}", "group_info_failed": "Group info failed", - "group_unknown": "Groupe {group:s} unknown", - "group_updated": "Groupe updated", - "group_update_failed": "groupe update failed", + "group_unknown": "Group {group:s} unknown", + "group_updated": "Group updated", + "group_update_failed": "Group update failed", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", "hook_list_by_invalid": "Invalid property to list hook by", @@ -274,7 +274,7 @@ "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", "log_user_permission_add": "Update '{}' permission", - "log_user_permission_remove": "Update '{}' permisson", + "log_user_permission_remove": "Update '{}' permission", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_migrations_migrate_backward": "Migrate backward", @@ -336,13 +336,13 @@ "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0009_backup_before_migration": "Make a backup of LDAP and apps settings before the migration", - "migration_0009_can_not_backup_before_migration": "The backup of the system before the migration failled. Migration failed. Error : {error:s}", + "migration_0009_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0009_create_group": "Create group for each user.", "migration_0009_done": "Migration sucess. You are now able to use groups of user.", - "migration_0009_failled": "Migration failed.", - "migration_0009_LDAP_update_failled": "LDAP update failled. Error : {error:s}", + "migration_0009_failed": "Migration failed.", + "migration_0009_LDAP_update_failed": "LDAP update failed. Error: {error:s}", "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", - "migration_0009_migration_failled_try_rollback": "Migration failed. Try to restore the system. Error : {error:s}", + "migration_0009_migration_failed_try_rollback": "Migration failed. Try to restore the system. Error: {error:s}", "migration_0009_rollback_success": "System restored.", "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", "migration_0009_update_LDAP_schema": "Update LDAP schema", @@ -374,7 +374,7 @@ "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 by 'yunohost user permission add -u USER' before to remove an allowed group", + "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", @@ -411,7 +411,7 @@ "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_failled": "Permission creation failed", + "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}", @@ -419,7 +419,7 @@ "permission_update_failed": "Permission update failed", "permission_generated": "Permission updated", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", - "permission_update_nothing_to_do": "Permission update nothing to do", + "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", @@ -527,7 +527,7 @@ "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", - "user_alread_in_group": "User {user:} already in group {group:s}", + "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", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 2d3ef5905..2baa5f465 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -42,7 +42,7 @@ def migrate_LDAP_db(auth): for rdn, attr_dict in ldap_map['depends_children'].items(): auth.add(rdn, attr_dict) except Exception as e: - raise YunohostError("migration_0009_LDAP_update_failled", error=e) + raise YunohostError("migration_0009_LDAP_update_failed", error=e) logger.info(m18n.n("migration_0009_create_group")) @@ -126,7 +126,7 @@ class MyMigration(Migration): permission_sync_to_user(auth) except Exception as e: - logger.warn(m18n.n("migration_0009_migration_failled_try_rollback", error=e)) + logger.warn(m18n.n("migration_0009_migration_failed_try_rollback", error=e)) os.system("systemctl stop slapd") os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) @@ -135,7 +135,7 @@ class MyMigration(Migration): os.system("systemctl start slapd") os.system("rm -r " + backup_folder) logger.info(m18n.n("migration_0009_rollback_success")) - raise YunohostError("migration_0009_failled") + raise YunohostError("migration_0009_failed") os.system("rm -r " + backup_folder) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index e490c33bd..61615cbb5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -204,7 +204,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ raise YunohostError('need_define_permission_before') group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' if not group_name in new_per_dict[permission_name]: - logger.warning(m18n.n('group_alread_disallowed', permission=per, app=a, group=g)) + logger.warning(m18n.n('group_already_disallowed', permission=per, app=a, group=g)) else: new_per_dict[permission_name].remove(group_name) @@ -213,7 +213,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ 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_alread_allowed', permission=per, app=a, group=g)) + logger.warning(m18n.n('group_already_allowed', permission=per, app=a, group=g)) else: new_per_dict[permission_name].add(group_name) @@ -363,7 +363,7 @@ def permission_add(operation_logger, auth, app, permission, urls=None, default_a logger.success(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise YunohostError('permission_creation_failled') + raise YunohostError('permission_creation_failed') @is_unit_operation(['permission','app']) @@ -418,7 +418,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem logger.success(m18n.n('permission_updated', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise YunohostError('premission_update_failled') + raise YunohostError('premission_update_failed') @is_unit_operation(['permission','app']) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 5e58909f7..442f499d3 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -648,7 +648,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u raise YunohostError('user_unknown', user=user) userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if userDN in group['member']: - logger.warning(m18n.n('user_alread_in_group', user=user, group=groupname)) + logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) new_group_list['member'].add(userDN) if remove_user: From 76ad77f829895cc4853db8a83079afdc934547ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 20 Feb 2019 23:02:18 +0100 Subject: [PATCH 29/58] Define permission in app_clearaccess --- 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 94f6680ce..badebf83f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1034,7 +1034,7 @@ def app_clearaccess(operation_logger, auth, apps): """ from yunohost.permission import user_permission_clear - user_permission_clear(operation_logger, auth, app=apps, permission="main") + permission = user_permission_clear(operation_logger, auth, app=apps, permission="main") result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} From 96b65f938b3dd1a326a1b2b7d151b5fc242c2ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 20 Feb 2019 23:07:17 +0100 Subject: [PATCH 30/58] Fix dirty fix ssowatconf --- src/yunohost/backup.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c22182a45..841b81db7 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -919,11 +919,6 @@ class RestoreManager(): successfull_apps = self.targets.list("apps", include=["Success", "Warning"]) - if successfull_apps != []: - # Quickfix: the old app_ssowatconf(auth) instruction failed due to - # ldap restore hooks - os.system('sudo yunohost app ssowatconf') - permission_sync_to_user(auth, force=True) if os.path.ismount(self.work_dir): From dd89391f0174b58213fa209348723d01794016fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 21 Feb 2019 09:31:29 +0100 Subject: [PATCH 31/58] Add more explicit args for permissions --- src/yunohost/data_migrations/0009_setup_group_permission.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 2baa5f465..0a21c2dfe 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -56,7 +56,7 @@ def migrate_LDAP_db(auth): {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) user_group_add(auth, username, gid=user_info['uidNumber'][0], sync_perm=False) user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=False) + user_group_update(auth, groupname='all_users', add_user=username, force=True, sync_perm=False) def migrate_app_permission(auth, app=None): @@ -79,7 +79,7 @@ def migrate_app_permission(auth, app=None): permission_add(auth, app, 'main', urls=[url], default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') - user_permission_add(auth, [app], 'main', group=allowed_group, sync_perm=False) + user_permission_add(auth, [app], permission='main', group=allowed_group, sync_perm=False) app_setting(app, 'allowed_users', delete=True) From 81e1d163f4fc24a5352915ea33999a8f13dc9868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 21 Feb 2019 09:35:17 +0100 Subject: [PATCH 32/58] Add more explicit args for permissions --- src/yunohost/data_migrations/0009_setup_group_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 0a21c2dfe..47d0d53a2 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -76,7 +76,7 @@ def migrate_app_permission(auth, app=None): url = None if domain and path: url = domain + path - permission_add(auth, app, 'main', urls=[url], default_allow=True, sync_perm=False) + permission_add(auth, app, permission='main', urls=[url], default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_add(auth, [app], permission='main', group=allowed_group, sync_perm=False) From 59714fdb1b264d72786fbbb15d7ca43406ddb2be Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:11:06 +0100 Subject: [PATCH 33/58] If url is None, we want to feed None and not [None] --- src/yunohost/data_migrations/0009_setup_group_permission.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 47d0d53a2..73be422b3 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -73,10 +73,8 @@ def migrate_app_permission(auth, app=None): path = app_setting(app, 'path') domain = app_setting(app, 'domain') - url = None - if domain and path: - url = domain + path - permission_add(auth, app, permission='main', urls=[url], default_allow=True, sync_perm=False) + urls = [domain + path] if domain and path else None + permission_add(auth, app, permission='main', urls=urls, default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_add(auth, [app], permission='main', group=allowed_group, sync_perm=False) From 47e6bf95dd6e3fe62a1b1bf77f62685b6935c232 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:31:01 +0100 Subject: [PATCH 34/58] Improve exception handling ... the 'real' traceback is shown by the parent scope, but was lost in the current code because we triggered another exception --- locales/en.json | 5 ++--- src/yunohost/data_migrations/0009_setup_group_permission.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index c58692f01..62c6603c1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -339,11 +339,10 @@ "migration_0009_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0009_create_group": "Create group for each user.", "migration_0009_done": "Migration sucess. You are now able to use groups of user.", - "migration_0009_failed": "Migration failed.", "migration_0009_LDAP_update_failed": "LDAP update failed. Error: {error:s}", "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", - "migration_0009_migration_failed_try_rollback": "Migration failed. Try to restore the system. Error: {error:s}", - "migration_0009_rollback_success": "System restored.", + "migration_0009_migration_failed_trying_to_rollback": "Migration failed ... trying to rollback the system.", + "migration_0009_rollback_success": "Rollback succeeded.", "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", "migration_0009_update_LDAP_schema": "Update LDAP schema", "migrations_backward": "Migrating backward.", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 73be422b3..9176d2969 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -124,7 +124,7 @@ class MyMigration(Migration): permission_sync_to_user(auth) except Exception as e: - logger.warn(m18n.n("migration_0009_migration_failed_try_rollback", error=e)) + logger.warn(m18n.n("migration_0009_migration_failed_trying_to_rollback")) os.system("systemctl stop slapd") os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) @@ -133,7 +133,7 @@ class MyMigration(Migration): os.system("systemctl start slapd") os.system("rm -r " + backup_folder) logger.info(m18n.n("migration_0009_rollback_success")) - raise YunohostError("migration_0009_failed") + raise os.system("rm -r " + backup_folder) From f845187230db6694783f74ce87186370f03f4367 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:32:14 +0100 Subject: [PATCH 35/58] Let's be paranoid and include all that stuff in the try/except as well --- .../0009_setup_group_permission.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 9176d2969..a1f4ca6ee 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -104,18 +104,18 @@ class MyMigration(Migration): except Exception as e: raise YunohostError("migration_0009_can_not_backup_before_migration", error=e) - # Update LDAP schema restart slapd - logger.info(m18n.n("migration_0009_update_LDAP_schema")) - service_regen_conf(names=['slapd'], force=True) - - # Do the authentication to LDAP after LDAP as been updated - AUTH_IDENTIFIER = ('ldap', 'as-root') - AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', - 'base_dn': 'dc=yunohost,dc=org', - 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} - auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) - try: + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0009_update_LDAP_schema")) + service_regen_conf(names=['slapd'], force=True) + + # Do the authentication to LDAP after LDAP as been updated + AUTH_IDENTIFIER = ('ldap', 'as-root') + AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + #Update LDAP database migrate_LDAP_db(auth) @@ -134,7 +134,7 @@ class MyMigration(Migration): os.system("rm -r " + backup_folder) logger.info(m18n.n("migration_0009_rollback_success")) raise + else: + os.system("rm -r " + backup_folder) - os.system("rm -r " + backup_folder) - - logger.info(m18n.n("migration_0009_done")) + logger.info(m18n.n("migration_0009_done")) From d49b098e67b5e017eb0d7965233a91c71f845a96 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:44:16 +0100 Subject: [PATCH 36/58] Specify group name in group-related messages --- locales/en.json | 12 ++++++------ src/yunohost/user.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/locales/en.json b/locales/en.json index 62c6603c1..d643f07fa 100644 --- a/locales/en.json +++ b/locales/en.json @@ -214,15 +214,15 @@ "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_name_already_exist": "Group {name:s} already exist", - "group_created": "Group creation success", - "group_creation_failed": "Group creation failed", - "group_deleted": "Group deleted", - "group_deletion_failed": "Group deletion failed", + "group_created": "Group '{group}' succesfully created", + "group_creation_failed": "Group creation failed for group '{group}'", + "group_deleted": "Group '{group}' deleted", + "group_deletion_failed": "Group '{group} 'deletion failed", "group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}", "group_info_failed": "Group info failed", "group_unknown": "Group {group:s} unknown", - "group_updated": "Group updated", - "group_update_failed": "Group update failed", + "group_updated": "Group '{group}' updated", + "group_update_failed": "Group update failed for group '{group}'", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", "hook_list_by_invalid": "Invalid property to list hook by", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 442f499d3..e1f706577 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -575,12 +575,12 @@ def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): } if auth.add('cn=%s,ou=groups' % groupname, attr_dict): - logger.success(m18n.n('group_created')) + logger.success(m18n.n('group_created', group=groupname)) if sync_perm: permission_sync_to_user(auth) return {'name': groupname} - raise YunohostError('group_creation_failed') + raise YunohostError('group_creation_failed', group=groupname) @is_unit_operation([('groupname', 'user')]) @@ -599,9 +599,9 @@ def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm= operation_logger.start() if not auth.remove('cn=%s,ou=groups' % groupname): - raise YunohostError('group_deletion_failed') + raise YunohostError('group_deletion_failed', group=groupname) - logger.success(m18n.n('group_deleted')) + logger.success(m18n.n('group_deleted', group=groupname)) if sync_perm: permission_sync_to_user(auth) @@ -677,9 +677,9 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if new_group_list['member'] != set(group['member']): if not auth.update('cn=%s,ou=groups' % groupname, new_group_list): - raise YunohostError('group_update_failed') + raise YunohostError('group_update_failed', group=groupname) - logger.success(m18n.n('group_updated')) + logger.success(m18n.n('group_updated', group=groupname)) if sync_perm: permission_sync_to_user(auth) return user_group_info(auth, groupname) From 81add41fddb40a126d4cf439486eae175c73d5a6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 01:33:50 +0100 Subject: [PATCH 37/58] Misc wording / formatting --- locales/en.json | 2 +- src/yunohost/permission.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index d643f07fa..6f9d28a40 100644 --- a/locales/en.json +++ b/locales/en.json @@ -416,7 +416,7 @@ "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": "Permission updated", + "permission_generated": "The permission database has been updated.", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 61615cbb5..aabf7f7a0 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -445,11 +445,14 @@ def permission_remove(operation_logger, auth, app, permission, force=False, sync def permission_sync_to_user(auth, force=False): """ - Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link + 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 wich use "slapadd" which don't use the memberOf overlay. - Note that by removing all value and adding a new time, we force the overlay to update all attributes + 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 @@ -505,6 +508,6 @@ def permission_sync_to_user(auth, force=False): app_ssowatconf(auth) - # Reload unscd because if not the group is not updated in the system from LDAP + # Reload unscd, otherwise the group ain't propagated to the LDAP database os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') From b7aa7f6fbb8f8f02e5cfb51f977fcaa9e7daa9ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 01:43:19 +0100 Subject: [PATCH 38/58] Cover small edge case where domain key doesnt exist --- 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 badebf83f..5e291bcec 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -911,7 +911,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on # Add path in permission if it's defined in the app install script app_settings = _get_app_settings(app_instance_name) - domain = app_settings['domain'] + domain = app_settings.get('domain', None) path = app_settings.get('path', None) if domain and path: permission_update(auth, app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) From c3e92f245fbde586d39ea22c2328336589b26e9d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 01:43:33 +0100 Subject: [PATCH 39/58] Use _get_migration_by_name to fetch migration module --- src/yunohost/backup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 841b81db7..6c37d7f00 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -903,12 +903,12 @@ class RestoreManager(): '(&(objectclass=groupOfNamesYnh)(cn=all_users))', ['cn']) if not result: - from importlib import import_module - migration_0009 = import_module('yunohost.data_migrations.0009_setup_group_permission') + from yunohost.tools import _get_migration_by_name + setup_group_permission = _get_migration_by_name("setup_group_permission") # Update LDAP schema restart slapd logger.info(m18n.n("migration_0009_update_LDAP_schema")) service_regen_conf(names=['slapd'], force=True) - migration_0009.migrate_LDAP_db(auth) + setup_group_permission.migrate_LDAP_db(auth) def clean(self, auth): """ @@ -1305,9 +1305,9 @@ class RestoreManager(): if os.path.isfile(app_restore_script_in_archive): os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) else: - from importlib import import_module - migration_0009 = import_module('yunohost.data_migrations.0009_setup_group_permission') - migration_0009.migrate_app_permission(auth, app=app_instance_name) + from yunohost.tools import _get_migration_by_name + setup_group_permission = _get_migration_by_name("setup_group_permission") + setup_group_permission.migrate_app_permission(auth, app=app_instance_name) # Copy the app scripts to a writable temporary folder # FIXME : use 'install -Dm555' or something similar to what's done From 4d126ad73669b67886b3895522e1808fcc744fdb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 01:57:24 +0100 Subject: [PATCH 40/58] Misc simplification / explicit code --- src/yunohost/user.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index e1f706577..8e2fdb295 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -214,7 +214,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Create group for user and add to group 'all_users' user_group_add(auth, groupname=username, gid=uid, sync_perm=False) user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=True) + user_group_update(auth, groupname='all_users', add_user=username, force=True, sync_perm=True) # TODO: Send a welcome mail to user logger.success(m18n.n('user_created')) @@ -290,17 +290,17 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None, from yunohost.app import app_ssowatconf from yunohost.utils.password import assert_password_is_strong_enough - attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop'] - new_attr_dict = {} domains = domain_list(auth)['domains'] # Populate user informations + attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop'] result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch) if not result: raise YunohostError('user_unknown', user=username) user = result[0] # Get modifications from arguments + new_attr_dict = {} if firstname: new_attr_dict['givenName'] = firstname # TODO: Validate new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + user['sn'][0] @@ -594,7 +594,10 @@ def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm= """ from yunohost.permission import permission_sync_to_user - if not force and (groupname == 'all_users' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): + if not force \ + and (groupname == 'all_users' or + groupname == 'admins' or + groupname in user_list(auth, fields=['uid'])['users']): raise YunohostError('group_deletion_not_allowed', user=groupname) operation_logger.start() @@ -620,12 +623,11 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u from yunohost.permission import permission_sync_to_user - attrs_to_fetch = ['member'] - if (groupname == 'all_users' or groupname == 'admins') and not force: raise YunohostError('edit_group_not_allowed', group=groupname) # Populate group informations + attrs_to_fetch = ['member'] result = auth.search(base='ou=groups,dc=yunohost,dc=org', filter='cn=' + groupname, attrs=attrs_to_fetch) if not result: @@ -638,13 +640,13 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u else: group['member'] = [] - user_l = user_list(auth, ['uid'])['users'] + existing_users = user_list(auth, fields=['uid'])['users'] if add_user: if not isinstance(add_user, list): add_user = [add_user] for user in add_user: - if not user in user_l: + if not user in existing_users: raise YunohostError('user_unknown', user=user) userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if userDN in group['member']: @@ -700,16 +702,16 @@ def user_group_info(auth, groupname): if not result: raise YunohostError('group_unknown', group=groupname) - else: - group = result[0] - result_dict = { - 'groupname': group['cn'][0], - 'member': None - } - if 'member' in group: - result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} - return result_dict + group = result[0] + + result_dict = { + 'groupname': group['cn'][0], + 'member': None + } + if 'member' in group: + result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} + return result_dict # # Permission subcategory From 13c6c9e5004464dd6520e08b391218bcea63d556 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 02:07:17 +0100 Subject: [PATCH 41/58] Factorize validation from actual action --- src/yunohost/user.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 8e2fdb295..3e818f7ea 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -645,9 +645,12 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if add_user: if not isinstance(add_user, list): add_user = [add_user] + for user in add_user: if not user 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']: logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) @@ -656,10 +659,13 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if remove_user: if not isinstance(remove_user, list): remove_user = [remove_user] + for user in remove_user: - userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if user == groupname: 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: From 1d4dfd52424e8f8d87ade6d07f8cbabdf880327a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 02:12:40 +0100 Subject: [PATCH 42/58] Clarify condition using a list --- locales/en.json | 2 +- src/yunohost/user.py | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6f9d28a40..d3bdeed04 100644 --- a/locales/en.json +++ b/locales/en.json @@ -218,7 +218,7 @@ "group_creation_failed": "Group creation failed for group '{group}'", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Group '{group} 'deletion failed", - "group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}", + "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", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 3e818f7ea..d6981ed36 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -594,11 +594,9 @@ def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm= """ from yunohost.permission import permission_sync_to_user - if not force \ - and (groupname == 'all_users' or - groupname == 'admins' or - groupname in user_list(auth, fields=['uid'])['users']): - raise YunohostError('group_deletion_not_allowed', user=groupname) + forbidden_groups = ["all_users", "admins"] + user_list(auth, fields=['uid'])['users'].keys() + if not force and groupname in forbidden_groups: + raise YunohostError('group_deletion_not_allowed', group=groupname) operation_logger.start() if not auth.remove('cn=%s,ou=groups' % groupname): @@ -640,7 +638,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u else: group['member'] = [] - existing_users = user_list(auth, fields=['uid'])['users'] + existing_users = user_list(auth, fields=['uid'])['users'].keys() if add_user: if not isinstance(add_user, list): From b782a1a6f0e138b665e7b847e78c1b8e0f6057c2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 02:57:00 +0100 Subject: [PATCH 43/58] Typos --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index d3bdeed04..7ec08cba2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -214,7 +214,7 @@ "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_name_already_exist": "Group {name:s} already exist", - "group_created": "Group '{group}' succesfully created", + "group_created": "Group '{group}' successfully created", "group_creation_failed": "Group creation failed for group '{group}'", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Group '{group} 'deletion failed", @@ -416,7 +416,7 @@ "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_generated": "The permission database has been updated", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", From bca4e39b2466d6bda68c489ef456a384b0bef991 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 03:13:14 +0100 Subject: [PATCH 44/58] Make the PEP gods happy --- .../0009_setup_group_permission.py | 11 ++--- src/yunohost/permission.py | 47 +++++++++---------- src/yunohost/user.py | 37 +++++++++------ 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index a1f4ca6ee..3c6958cac 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -1,7 +1,6 @@ import yaml import time import os -import shutil from moulinette import m18n from moulinette.core import init_authenticator @@ -9,8 +8,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration -from yunohost.utils.filesystem import free_space_in_directory, space_used_by_directory -from yunohost.user import user_list, user_group_add, user_group_update +from yunohost.user import user_group_add, user_group_update from yunohost.app import app_setting, app_list from yunohost.service import service_regen_conf from yunohost.permission import permission_add, permission_sync_to_user @@ -22,6 +20,7 @@ logger = getActionLogger('yunohost.migration') # Tools used also for restoration ################################################### + def migrate_LDAP_db(auth): logger.info(m18n.n("migration_0009_update_LDAP_database")) try: @@ -46,7 +45,7 @@ def migrate_LDAP_db(auth): logger.info(m18n.n("migration_0009_create_group")) - #Create a group for each yunohost user + # Create a group for each yunohost user user_list = auth.search('ou=users,dc=yunohost,dc=org', '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', ['uid', 'uidNumber']) @@ -116,7 +115,7 @@ class MyMigration(Migration): 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) - #Update LDAP database + # Update LDAP database migrate_LDAP_db(auth) # Migrate permission @@ -126,7 +125,7 @@ class MyMigration(Migration): except Exception as e: logger.warn(m18n.n("migration_0009_migration_failed_trying_to_rollback")) os.system("systemctl stop slapd") - os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config + os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder) os.system("cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" % backup_folder) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index aabf7f7a0..57de63f25 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -30,11 +30,12 @@ import random from moulinette import m18n from moulinette.utils.log import getActionLogger from yunohost.utils.error import YunohostError -from yunohost.user import user_list, user_group_list +from yunohost.user import user_list from yunohost.log import is_unit_operation logger = getActionLogger('yunohost.user') + def user_permission_list(auth, app=None, permission=None, username=None, group=None): """ List permission for specific application @@ -47,8 +48,6 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N """ - user_l = user_list(auth, ['uid'])['users'] - permission_attrs = [ 'cn', 'groupPermission', @@ -86,20 +85,20 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N for u in res['inheritPermission']: user_name.append(u.split("=")[1].split(",")[0]) - # Don't show the result if the user diffined a specific permission, user or group - if app and not app_name in app: + # 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 not permission_name in permission: + 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 not app_name in permissions: + if app_name not in permissions: permissions[app_name] = {} - permissions[app_name][permission_name] = {'allowed_users':[], 'allowed_groups':[]} + 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: @@ -160,16 +159,16 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ # Validate that the group exist for g in add_group: - if not g in user_group_list(auth, ['cn'])['groups']: + if g not in user_group_list(auth, ['cn'])['groups']: raise YunohostError('group_unknown', group=g) for u in add_username: - if not u in user_list(auth, ['uid'])['users']: + if u not in user_list(auth, ['uid'])['users']: raise YunohostError('user_unknown', user=u) for g in del_group: - if not g in user_group_list(auth, ['cn'])['groups']: + if g not in user_group_list(auth, ['cn'])['groups']: raise YunohostError('group_unknown', group=g) for u in del_username: - if not u in user_list(auth, ['uid'])['users']: + if u not in user_list(auth, ['uid'])['users']: raise YunohostError('user_unknown', user=u) # Merge user and group (note that we consider all user as a group) @@ -193,7 +192,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ for a in app: for per in permission: permission_name = per + '.' + a - if not permission_name in result: + 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]: @@ -203,7 +202,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ 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 not group_name in new_per_dict[permission_name]: + 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) @@ -287,11 +286,11 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_ for a in app: for per in permission: permission_name = per + '.' + a - if not permission_name in result: + 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 + logger.warning(m18n.n('permission_already_clear', permission=per, app=a)) + continue if auth.update('cn=%s,ou=permission' % permission_name, default_permission): logger.success(m18n.n('permission_updated', permission=per, app=a)) else: @@ -311,7 +310,7 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_ return user_permission_list(auth, app, permission) -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def permission_add(operation_logger, auth, app, permission, urls=None, default_allow=True, sync_perm=True): """ Create a new permission for a specific application @@ -325,7 +324,7 @@ def permission_add(operation_logger, auth, app, permission, urls=None, default_a from yunohost.domain import _normalize_domain_path # Validate uniqueness of permission in LDAP - permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + permission_name = str(permission + '.' + app) # str(...) Fix encoding issue conflict = auth.get_conflict({ 'cn': permission_name }, base_dn='ou=permission,dc=yunohost,dc=org') @@ -366,7 +365,7 @@ def permission_add(operation_logger, auth, app, permission, urls=None, default_a raise YunohostError('permission_creation_failed') -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def permission_update(operation_logger, auth, app, permission, add_url=None, remove_url=None, sync_perm=True): """ Update a permission for a specific application @@ -380,7 +379,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem """ from yunohost.domain import _normalize_domain_path - permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + permission_name = str(permission + '.' + app) # str(...) Fix encoding issue # Populate permission informations result = auth.search(base='ou=permission,dc=yunohost,dc=org', @@ -389,7 +388,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem raise YunohostError('permission_not_found', permission=permission, app=app) permission_obj = result[0] - if not 'URL' in permission_obj: + if 'URL' not in permission_obj: permission_obj['URL'] = [] url = set(permission_obj['URL']) @@ -412,7 +411,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem return user_permission_list(auth, app, permission) operation_logger.start() - if auth.update('cn=%s,ou=permission' % permission_name, {'cn':permission_name, 'URL': url}): + if auth.update('cn=%s,ou=permission' % permission_name, {'cn': permission_name, 'URL': url}): if sync_perm: permission_sync_to_user(auth) logger.success(m18n.n('permission_updated', permission=permission, app=app)) @@ -421,7 +420,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem raise YunohostError('premission_update_failed') -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def permission_remove(operation_logger, auth, app, permission, force=False, sync_perm=True): """ Remove a permission for a specific application diff --git a/src/yunohost/user.py b/src/yunohost/user.py index d6981ed36..0fca858a3 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -209,7 +209,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas except subprocess.CalledProcessError: if not os.path.isdir('/home/{0}'.format(username)): logger.warning(m18n.n('user_home_creation_failed'), - exc_info=1) + exc_info=1) # Create group for user and add to group 'all_users' user_group_add(auth, groupname=username, gid=uid, sync_perm=False) @@ -220,7 +220,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas logger.success(m18n.n('user_created')) hook_callback('post_user_create', - args=[username, mail, password, firstname, lastname]) + args=[username, mail, password, firstname, lastname]) return {'fullname': fullname, 'username': username, 'mail': mail} @@ -469,10 +469,10 @@ def user_info(auth, username): else: raise YunohostError('user_info_failed') + # # Group subcategory # -# def user_group_list(auth, fields=None): """ List users @@ -485,9 +485,9 @@ def user_group_list(auth, fields=None): """ group_attr = { - 'cn' : 'groupname', - 'member' : 'members', - 'permission' : 'permission' + 'cn': 'groupname', + 'member': 'members', + 'permission': 'permission' } attrs = ['cn'] groups = {} @@ -531,11 +531,12 @@ def user_group_list(auth, fields=None): groupname = entry[group_attr['cn']] groups[groupname] = entry - return {'groups' : groups} + + return {'groups': groups} @is_unit_operation([('groupname', 'user')]) -def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): +def user_group_add(operation_logger, auth, groupname, gid=None, sync_perm=True): """ Create group @@ -645,7 +646,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u add_user = [add_user] for user in add_user: - if not user in existing_users: + if user not in existing_users: raise YunohostError('user_unknown', user=user) for user in add_user: @@ -717,38 +718,44 @@ def user_group_info(auth, groupname): result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} return result_dict + # # Permission subcategory # -# -import yunohost.permission def user_permission_list(auth, app=None, permission=None, username=None, group=None, sync_perm=True): + import yunohost.permission return yunohost.permission.user_permission_list(auth, app, permission, username, group) + @is_unit_operation([('app', 'user')]) def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None, sync_perm=True): + import yunohost.permission return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, - add_username=username, add_group=group, - del_username=None, del_group=None, - sync_perm=sync_perm) + 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, auth, app, permission="main", username=None, group=None, sync_perm=True): + import yunohost.permission return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, add_username=None, add_group=None, del_username=username, del_group=group, sync_perm=sync_perm) + @is_unit_operation([('app', 'user')]) def user_permission_clear(operation_logger, auth, app, permission=None, sync_perm=True): + import yunohost.permission return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission, sync_perm=sync_perm) + # # SSH subcategory # -# import yunohost.ssh From 975341a0b5d22cb6bb7090e60eb9b01ddc2b18ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 10 May 2019 22:02:06 +0200 Subject: [PATCH 45/58] Inline function _migrate_system_if_needed in backup --- src/yunohost/backup.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 713eb3761..0712894c6 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -893,22 +893,6 @@ class RestoreManager(): logger.debug("executing the post-install...") tools_postinstall(domain, 'Yunohost', True) - def _migrate_system_if_needed(self, auth): - """ - Do some migration if needed - """ - - # Check if we need to do the migration 0009 : setup group and permission - result = auth.search('ou=groups,dc=yunohost,dc=org', - '(&(objectclass=groupOfNamesYnh)(cn=all_users))', - ['cn']) - if not result: - from yunohost.tools import _get_migration_by_name - setup_group_permission = _get_migration_by_name("setup_group_permission") - # Update LDAP schema restart slapd - logger.info(m18n.n("migration_0009_update_LDAP_schema")) - service_regen_conf(names=['slapd'], force=True) - setup_group_permission.migrate_LDAP_db(auth) def clean(self, auth): """ @@ -1130,7 +1114,6 @@ class RestoreManager(): self._patch_backup_csv_file() self._restore_system(auth) - self._migrate_system_if_needed(auth) self._restore_apps(auth) finally: self.clean(auth) @@ -1215,6 +1198,20 @@ class RestoreManager(): service_regen_conf() + # Check if we need to do the migration 0009 : setup group and permission + # Legacy code + result = auth.search('ou=groups,dc=yunohost,dc=org', + '(&(objectclass=groupOfNamesYnh)(cn=all_users))', + ['cn']) + if not result: + from yunohost.tools import _get_migration_by_name + setup_group_permission = _get_migration_by_name("setup_group_permission") + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0009_update_LDAP_schema")) + service_regen_conf(names=['slapd'], force=True) + setup_group_permission.migrate_LDAP_db(auth) + + def _restore_apps(self, auth): """Restore all apps targeted""" From d0f422ee39c4d5639d91691eb677dd77b0825af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 10 May 2019 22:11:31 +0200 Subject: [PATCH 46/58] Don't force ldap_sync in app install --- src/yunohost/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0b5612d1a..dddd0e390 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -845,7 +845,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) # Create permission before the install (useful if the install script redefine the permission) - permission_add(auth, app=app_instance_name, permission="main") + # 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(auth, app=app_instance_name, permission="main", sync_perm=False) # Execute the app install script install_retcode = 1 From fd76f5544acc0a0fa49bcd1fed6d22ccedb26624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 10 May 2019 22:22:57 +0200 Subject: [PATCH 47/58] Get the permission name more elegent --- src/yunohost/permission.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 57de63f25..4096d5465 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -71,11 +71,10 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N '(objectclass=permissionYnh)', permission_attrs) for res in result: - permission_name = res['cn'][0].split('.')[0] try: - app_name = res['cn'][0].split('.')[1] + permission_name, app_name = res['cn'][0].split('.') except: - logger.warning(m18n.n('permission_name_not_valid', permission=per)) + logger.warning(m18n.n('permission_name_not_valid', permission=res['cn'][0])) group_name = [] if 'groupPermission' in res: for g in res['groupPermission']: From 6674c9d59d4dfe3690c45dd61480c8bee3feb759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 10 May 2019 22:38:56 +0200 Subject: [PATCH 48/58] Restart slapd in all case in migration --- src/yunohost/data_migrations/0009_setup_group_permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 3c6958cac..70004925e 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -99,9 +99,10 @@ class MyMigration(Migration): os.system("cp -r --preserve /etc/ldap %s/ldap_config" % backup_folder) os.system("cp -r --preserve /var/lib/ldap %s/ldap_db" % backup_folder) os.system("cp -r --preserve /etc/yunohost/apps %s/apps_settings" % backup_folder) - os.system("systemctl start slapd") except Exception as e: raise YunohostError("migration_0009_can_not_backup_before_migration", error=e) + finally: + os.system("systemctl start slapd") try: # Update LDAP schema restart slapd From 0c0db4c8a4c3c9526eccd11d6c28bbf91e0eb776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 10 May 2019 23:47:08 +0200 Subject: [PATCH 49/58] Check if the migration is possible --- locales/en.json | 2 +- .../data_migrations/0011_setup_group_permission.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index ff4155924..31513a546 100644 --- a/locales/en.json +++ b/locales/en.json @@ -357,6 +357,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": "Create group for each user.", "migration_0011_done": "Migration sucess. You are now able to use groups of user.", + "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": "Migrate permission from apps settings to LDAP", "migration_0011_migration_failed_trying_to_rollback": "Migration failed ... trying to rollback the system.", @@ -460,7 +461,6 @@ "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Unable to regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", ->>>>>>> upstream/stretch-unstable "restore_action_required": "You must specify something to restore", "restore_already_installed_app": "An app is already installed with the id '{app:s}'", "restore_app_failed": "Unable to restore the app '{app:s}'", diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index 89bad6306..a4b37fd87 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 moulinette.utils.log import getActionLogger from yunohost.tools import Migration from yunohost.user import user_group_add, user_group_update from yunohost.app import app_setting, app_list -from yunohost.service import service_regen_conf +from yunohost.regenconf import regen_conf from yunohost.permission import permission_add, permission_sync_to_user from yunohost.user import user_permission_add @@ -90,6 +90,12 @@ class MyMigration(Migration): required = True def migrate(self): + # 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 + if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']: + raise YunohostError("migration_0011_LDAP_config_dirty") + # Backup LDAP and the apps settings before to do the migration logger.info(m18n.n("migration_0011_backup_before_migration")) try: @@ -107,7 +113,7 @@ class MyMigration(Migration): try: # Update LDAP schema restart slapd logger.info(m18n.n("migration_0011_update_LDAP_schema")) - service_regen_conf(names=['slapd'], force=True) + regen_conf(names=['slapd'], force=True) # Do the authentication to LDAP after LDAP as been updated AUTH_IDENTIFIER = ('ldap', 'as-root') From 3c1c32e08b6fac138dbabb9e9c655f92227f9449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 22 May 2019 22:45:28 +0200 Subject: [PATCH 50/58] [Fix] LDAP restauration in backup for apps --- 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 3973dcee4..879798099 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1322,7 +1322,7 @@ class RestoreManager(): filesystem.chown(app_scripts_new_path, 'admin', None, True) # Restore permissions - if os.path.isfile(app_restore_script_in_archive): + if os.path.isfile(app_settings_in_archive + '/permission.ldif'): os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) else: from yunohost.tools import _get_migration_by_name From bc0435b9c4cc9e596539d75434a111adf7cde34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 26 May 2019 00:52:06 +0200 Subject: [PATCH 51/58] Group-permission migration - integrate all function in migration class --- .../0011_setup_group_permission.py | 124 +++++++++--------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/src/yunohost/data_migrations/0011_setup_group_permission.py b/src/yunohost/data_migrations/0011_setup_group_permission.py index a4b37fd87..747de4546 100644 --- a/src/yunohost/data_migrations/0011_setup_group_permission.py +++ b/src/yunohost/data_migrations/0011_setup_group_permission.py @@ -20,66 +20,6 @@ logger = getActionLogger('yunohost.migration') # Tools used also for restoration ################################################### - -def migrate_LDAP_db(auth): - logger.info(m18n.n("migration_0011_update_LDAP_database")) - try: - auth.remove('cn=sftpusers,ou=groups') - except: - logger.warn(m18n.n("error_when_removing_sftpuser_group")) - - with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: - ldap_map = yaml.load(f) - - try: - attr_dict = ldap_map['parents']['ou=permission'] - auth.add('ou=permission', attr_dict) - - attr_dict = ldap_map['children']['cn=all_users,ou=groups'] - auth.add('cn=all_users,ou=groups', attr_dict) - - for rdn, attr_dict in ldap_map['depends_children'].items(): - auth.add(rdn, attr_dict) - except Exception as e: - raise YunohostError("migration_0011_LDAP_update_failed", error=e) - - logger.info(m18n.n("migration_0011_create_group")) - - # Create a group for each yunohost user - user_list = auth.search('ou=users,dc=yunohost,dc=org', - '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', - ['uid', 'uidNumber']) - for user_info in user_list: - username = user_info['uid'][0] - auth.update('uid=%s,ou=users' % username, - {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) - user_group_add(auth, username, gid=user_info['uidNumber'][0], sync_perm=False) - user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(auth, groupname='all_users', add_user=username, force=True, sync_perm=False) - - -def migrate_app_permission(auth, app=None): - logger.info(m18n.n("migration_0011_migrate_permission")) - - if app: - apps = app_list(installed=True, filter=app)['apps'] - else: - apps = app_list(installed=True)['apps'] - - for app_info in apps: - app = app_info['id'] - permission = app_setting(app, 'allowed_users') - path = app_setting(app, 'path') - domain = app_setting(app, 'domain') - - urls = [domain + path] if domain and path else None - permission_add(auth, app, permission='main', urls=urls, default_allow=True, sync_perm=False) - if permission: - allowed_group = permission.split(',') - user_permission_add(auth, [app], permission='main', group=allowed_group, sync_perm=False) - app_setting(app, 'allowed_users', delete=True) - - class MyMigration(Migration): """ Update the LDAP DB to be able to store the permission @@ -89,6 +29,66 @@ class MyMigration(Migration): required = True + def migrate_LDAP_db(self, auth): + print("asdfadsf") + logger.info(m18n.n("migration_0011_update_LDAP_database")) + try: + auth.remove('cn=sftpusers,ou=groups') + except: + logger.warn(m18n.n("error_when_removing_sftpuser_group")) + + with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: + ldap_map = yaml.load(f) + + try: + attr_dict = ldap_map['parents']['ou=permission'] + auth.add('ou=permission', attr_dict) + + attr_dict = ldap_map['children']['cn=all_users,ou=groups'] + auth.add('cn=all_users,ou=groups', attr_dict) + + for rdn, attr_dict in ldap_map['depends_children'].items(): + auth.add(rdn, attr_dict) + except Exception as e: + raise YunohostError("migration_0011_LDAP_update_failed", error=e) + + logger.info(m18n.n("migration_0011_create_group")) + + # Create a group for each yunohost user + user_list = auth.search('ou=users,dc=yunohost,dc=org', + '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', + ['uid', 'uidNumber']) + for user_info in user_list: + username = user_info['uid'][0] + auth.update('uid=%s,ou=users' % username, + {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) + user_group_add(auth, username, gid=user_info['uidNumber'][0], sync_perm=False) + user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) + user_group_update(auth, groupname='all_users', add_user=username, force=True, sync_perm=False) + + + def migrate_app_permission(self, auth, app=None): + logger.info(m18n.n("migration_0011_migrate_permission")) + + if app: + apps = app_list(installed=True, filter=app)['apps'] + else: + apps = app_list(installed=True)['apps'] + + for app_info in apps: + app = app_info['id'] + permission = app_setting(app, 'allowed_users') + path = app_setting(app, 'path') + domain = app_setting(app, 'domain') + + urls = [domain + path] if domain and path else None + permission_add(auth, app, permission='main', urls=urls, default_allow=True, sync_perm=False) + if permission: + allowed_group = permission.split(',') + user_permission_add(auth, [app], permission='main', group=allowed_group, sync_perm=False) + app_setting(app, 'allowed_users', delete=True) + + def migrate(self): # Check if the migration can be processed ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) @@ -123,10 +123,10 @@ class MyMigration(Migration): auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) # Update LDAP database - migrate_LDAP_db(auth) + self.migrate_LDAP_db(auth) # Migrate permission - migrate_app_permission(auth) + self.migrate_app_permission(auth) permission_sync_to_user(auth) except Exception as e: From f839ec51537fc0fd4292b3bff75d1419e47787d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 26 May 2019 00:54:57 +0200 Subject: [PATCH 52/58] Improve restoration mecanisme for LDAP integrity --- data/hooks/backup/05-conf_ldap | 6 ++-- locales/en.json | 2 ++ src/yunohost/backup.py | 65 ++++++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap index 5b91bb5b2..9ae22095e 100755 --- a/data/hooks/backup/05-conf_ldap +++ b/data/hooks/backup/05-conf_ldap @@ -13,7 +13,5 @@ backup_dir="${1}/conf/ldap" ynh_backup "/etc/ldap/slapd.conf" "${backup_dir}/slapd.conf" sudo slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" -# Backup the database (all but not the permission except the permission for mail, metronome and sftp -sudo slapcat -b dc=yunohost,dc=org \ - -H 'ldap:///dc=yunohost,dc=org???(|(!(objectClass=permissionYnh))(cn=main.mail)(cn=main.metronome)(cn=main.sftp))' \ - -l "${backup_dir}/dc=yunohost-dc=org.ldif" +# Backup the database +sudo slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" diff --git a/locales/en.json b/locales/en.json index 31513a546..fc3f8fe69 100644 --- a/locales/en.json +++ b/locales/en.json @@ -48,6 +48,8 @@ "app_upgrade_failed": "Unable to upgrade {app:s}", "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 879798099..3087a15e9 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -919,7 +919,7 @@ class RestoreManager(): successfull_apps = self.targets.list("apps", include=["Success", "Warning"]) - permission_sync_to_user(auth, force=True) + permission_sync_to_user(auth, force=False) if os.path.ismount(self.work_dir): ret = subprocess.call(["umount", self.work_dir]) @@ -1183,6 +1183,16 @@ class RestoreManager(): if system_targets == []: return + # 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 = auth.search('ou=permission,dc=yunohost,dc=org', + '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))', + ['cn', 'objectClass', 'groupPermission', 'URL', 'gidNumber']) + except: + logger.info(m18n.n('apps_permission_not_found')) + # Start register change on system operation_logger = OperationLogger('backup_restore_system') operation_logger.start() @@ -1228,10 +1238,27 @@ class RestoreManager(): from yunohost.tools import _get_migration_by_name setup_group_permission = _get_migration_by_name("setup_group_permission") # Update LDAP schema restart slapd - logger.info(m18n.n("migration_0009_update_LDAP_schema")) - service_regen_conf(names=['slapd'], force=True) + logger.info(m18n.n("migration_0011_update_LDAP_schema")) + regen_conf(names=['slapd'], force=True) setup_group_permission.migrate_LDAP_db(auth) + # Remove all permission for all app which sill in the LDAP + for per in auth.search('ou=permission,dc=yunohost,dc=org', + '(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))', + ['cn']): + if not auth.remove('cn=%s,ou=permission' % per['cn'][0]): + raise YunohostError('permission_deletion_failed', permission=permission, app=app) + + # 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])) + if _is_installed(app_name): + if not auth.add('cn=%s,ou=permission' % per['cn'][0], per): + raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name) + def _restore_apps(self, auth): """Restore all apps targeted""" @@ -1239,6 +1266,7 @@ class RestoreManager(): apps_targets = self.targets.list("apps", exclude=["Skipped"]) for app in apps_targets: + print(app) self._restore_app(auth, app) def _restore_app(self, auth, app_instance_name): @@ -1268,6 +1296,9 @@ 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 + def copytree(src, dst, symlinks=False, ignore=None): for item in os.listdir(src): s = os.path.join(src, item) @@ -1321,14 +1352,6 @@ class RestoreManager(): filesystem.chmod(app_settings_new_path, 0o400, 0o400, True) filesystem.chown(app_scripts_new_path, 'admin', None, True) - # Restore permissions - if os.path.isfile(app_settings_in_archive + '/permission.ldif'): - os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) - else: - from yunohost.tools import _get_migration_by_name - setup_group_permission = _get_migration_by_name("setup_group_permission") - setup_group_permission.migrate_app_permission(auth, app=app_instance_name) - # Copy the app scripts to a writable temporary folder # FIXME : use 'install -Dm555' or something similar to what's done # in the backup method ? @@ -1338,6 +1361,26 @@ class RestoreManager(): filesystem.chown(tmp_folder_for_app_restore, 'admin', None, True) restore_script = os.path.join(tmp_folder_for_app_restore, 'restore') + # Restore permissions + 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(auth, ['cn'])['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) + print(entry) + if not auth.add('cn=%s,ou=permission' % entry['cn'][0], entry): + raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name) + else: + from yunohost.tools import _get_migration_by_name + setup_group_permission = _get_migration_by_name("setup_group_permission") + setup_group_permission.migrate_app_permission(auth, app=app_instance_name) + # Prepare env. var. to pass to script env_dict = self._get_env_var(app_instance_name) From d97e917ef95efdb7dfe8fdbaed905afc4e1b3f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 28 May 2019 20:24:37 +0200 Subject: [PATCH 53/58] Clean Permission if app restoration fail --- src/yunohost/backup.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 3087a15e9..5f6e3c258 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1298,6 +1298,7 @@ class RestoreManager(): """ from moulinette.utils.filesystem import read_ldif from yunohost.user import user_group_list + from yunohost.permission import permission_remove def copytree(src, dst, symlinks=False, ignore=None): for item in os.listdir(src): @@ -1427,6 +1428,13 @@ class RestoreManager(): # Cleaning app directory shutil.rmtree(app_settings_new_path, ignore_errors=True) + # Remove all permission in LDAP + result = auth.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_remove(auth, app_instance_name, l.split('.')[0], force=True) + # TODO Cleaning app hooks else: self.targets.set_result("apps", app_instance_name, "Success") From d5893162447647d27cb811588658f9cd075d6318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 28 May 2019 20:40:11 +0200 Subject: [PATCH 54/58] Add test for permission linked to each apps --- src/yunohost/tests/test_backuprestore.py | 8 +++++++- src/yunohost/tests/test_permission.py | 22 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index c8f6983cb..e75fb9581 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -14,7 +14,7 @@ from yunohost.backup import backup_create, backup_restore, backup_list, backup_i from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError from yunohost.user import user_permission_list -from yunohost.tests.test_permission import check_LDAP_db_integrity +from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps # Get main domain maindomain = "" @@ -98,6 +98,12 @@ def check_LDAP_db_integrity_call(): yield check_LDAP_db_integrity() +@pytest.fixture(autouse=True) +def check_permission_for_apps_call(): + check_permission_for_apps() + yield + check_permission_for_apps() + # # Helpers # # diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 2ed84f937..c7c8bdfba 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1,7 +1,7 @@ import pytest from moulinette.core import init_authenticator, MoulinetteError -from yunohost.app import app_install, app_remove, app_change_url +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.domain import _get_maindomain @@ -136,6 +136,26 @@ def check_LDAP_db_integrity(): allowed_user_list = [m.split("=")[1].split(",")[0] for m in permission_map[permission]['inheritPermission']] assert set(user_list) <= set(allowed_user_list) + +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 + permission_search = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', + ['cn', 'groupPermission', 'inheritPermission', 'memberUid']) + app_l = app_list(installed=True)['apps'] + apps_list_set = set() + permission_list_set = set() + for permission in permission_search: + permission_list_set.add(permission['cn'][0].split(".")[1]) + for app in app_l: + apps_list_set.add(app['id']) + extra_service_permission = set(['mail', 'metronome']) + if 'sftp' in permission_list_set: + extra_service_permission.add('sftp') + assert apps_list_set == permission_list_set - extra_service_permission + # # List functions # From cced69aa94976ed4a50866704be9b0fe2cb27a77 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 31 May 2019 18:21:04 +0200 Subject: [PATCH 55/58] Simplify code using set comprehensions --- src/yunohost/tests/test_permission.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index e782eb837..d309a8211 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -142,17 +142,14 @@ def check_permission_for_apps(): permission_search = ldap.search('ou=permission,dc=yunohost,dc=org', '(objectclass=permissionYnh)', ['cn', 'groupPermission', 'inheritPermission', 'memberUid']) - app_l = app_list(installed=True)['apps'] - apps_list_set = set() - permission_list_set = set() - for permission in permission_search: - permission_list_set.add(permission['cn'][0].split(".")[1]) - for app in app_l: - apps_list_set.add(app['id']) + + 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']) if 'sftp' in permission_list_set: extra_service_permission.add('sftp') - assert apps_list_set == permission_list_set - extra_service_permission + assert installed_apps == permission_list_set - extra_service_permission # # List functions From 1d9eec10423509ea641fd43631a66bcc12e020b3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 31 May 2019 18:40:44 +0200 Subject: [PATCH 56/58] Typo / wording of migration steps --- locales/en.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 69c03c27a..eae8621de 100644 --- a/locales/en.json +++ b/locales/en.json @@ -358,17 +358,17 @@ "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0009_not_needed": "This migration already happened somehow ? Skipping.", - "migration_0011_backup_before_migration": "Make a backup of LDAP and apps settings before the migration", + "migration_0011_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", "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": "Create group for each user.", - "migration_0011_done": "Migration sucess. You are now able to use groups of user.", + "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_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": "Migrate permission from apps settings to LDAP", + "migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...", "migration_0011_migration_failed_trying_to_rollback": "Migration failed ... trying to rollback the system.", "migration_0011_rollback_success": "Rollback succeeded.", - "migration_0011_update_LDAP_database": "Update LDAP database for groups and permission support", - "migration_0011_update_LDAP_schema": "Update LDAP schema", + "migration_0011_update_LDAP_database": "Updating LDAP database...", + "migration_0011_update_LDAP_schema": "Updating LDAP schema...", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", From ad3ebceff6422bcf058cbd5c8db0f1169c249c3e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 31 May 2019 18:45:21 +0200 Subject: [PATCH 57/58] Fix old 'auth' thing in app helpers --- data/helpers.d/setting | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index afaf10e7f..da711b4bd 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(auth, '$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_add; permission_add('$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(auth, '$app', '$permission', sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove('$app', '$permission', sync_perm=False)" } # Add a path managed by the SSO @@ -279,7 +279,7 @@ ynh_permission_add_path() { local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" } # Remove a path managed by the SSO @@ -295,5 +295,5 @@ ynh_permission_del_path() { local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" } From 06a5e29786b211f247e32e362a46a3c6c733e6bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 31 May 2019 18:59:23 +0200 Subject: [PATCH 58/58] Reclassify success messages as debug ? --- src/yunohost/app.py | 2 +- src/yunohost/permission.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f97857b9d..aadcf8a0e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1422,7 +1422,7 @@ def app_ssowatconf(): with open('/etc/ssowat/conf.json', 'w+') as f: json.dump(conf_dict, f, sort_keys=True, indent=4) - logger.success(m18n.n('ssowat_conf_generated')) + logger.debug(m18n.n('ssowat_conf_generated')) def app_change_label(app, new_label): diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 9d2091752..3beb3ac2b 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -228,7 +228,7 @@ def user_permission_update(operation_logger, app=[], permission=None, add_userna continue if ldap.update('cn=%s,ou=permission' % per, {'groupPermission': val}): p = per.split('.') - logger.success(m18n.n('permission_updated', permission=p[0], app=p[1])) + logger.debug(m18n.n('permission_updated', permission=p[0], app=p[1])) else: raise YunohostError('permission_update_failed') @@ -298,7 +298,7 @@ def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=T logger.warning(m18n.n('permission_already_clear', permission=per, app=a)) continue if ldap.update('cn=%s,ou=permission' % permission_name, default_permission): - logger.success(m18n.n('permission_updated', permission=per, app=a)) + logger.debug(m18n.n('permission_updated', permission=per, app=a)) else: raise YunohostError('permission_update_failed') @@ -367,7 +367,7 @@ def permission_add(operation_logger, app, permission, urls=None, default_allow=T if ldap.add('cn=%s,ou=permission' % permission_name, attr_dict): if sync_perm: permission_sync_to_user() - logger.success(m18n.n('permission_created', permission=permission, app=app)) + logger.debug(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(app, permission) raise YunohostError('permission_creation_failed') @@ -424,7 +424,7 @@ def permission_update(operation_logger, app, permission, add_url=None, remove_ur if ldap.update('cn=%s,ou=permission' % permission_name, {'cn': permission_name, 'URL': url}): if sync_perm: permission_sync_to_user() - logger.success(m18n.n('permission_updated', permission=permission, app=app)) + logger.debug(m18n.n('permission_updated', permission=permission, app=app)) return user_permission_list(app, permission) raise YunohostError('premission_update_failed') @@ -452,7 +452,7 @@ def permission_remove(operation_logger, app, permission, force=False, sync_perm= raise YunohostError('permission_deletion_failed', permission=permission, app=app) if sync_perm: permission_sync_to_user() - logger.success(m18n.n('permission_deleted', permission=permission, app=app)) + logger.debug(m18n.n('permission_deleted', permission=permission, app=app)) def permission_sync_to_user(force=False): @@ -518,7 +518,7 @@ def permission_sync_to_user(force=False): else: if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): raise YunohostError('permission_update_failed') - logger.success(m18n.n('permission_generated')) + logger.debug(m18n.n('permission_generated')) app_ssowatconf()