diff --git a/conf/slapd/db_init.ldif b/conf/slapd/db_init.ldif index 55fad59c6d..0c0ed842eb 100644 --- a/conf/slapd/db_init.ldif +++ b/conf/slapd/db_init.ldif @@ -68,28 +68,16 @@ groupPermission: cn=all_users,ou=groups,dc=yunohost,dc=org cn: mail.main objectClass: posixGroup objectClass: permissionYnh -isProtected: TRUE -label: E-mail gidNumber: 5001 -showTile: FALSE -authHeader: FALSE dn: cn=ssh.main,ou=permission,dc=yunohost,dc=org cn: ssh.main objectClass: posixGroup objectClass: permissionYnh -isProtected: TRUE -label: SSH gidNumber: 5003 -showTile: FALSE -authHeader: FALSE dn: cn=sftp.main,ou=permission,dc=yunohost,dc=org cn: sftp.main objectClass: posixGroup objectClass: permissionYnh -isProtected: TRUE -label: SFTP gidNumber: 5004 -showTile: FALSE -authHeader: FALSE diff --git a/conf/slapd/permission.ldif b/conf/slapd/permission.ldif index 64222db1d4..400d66cc5e 100644 --- a/conf/slapd/permission.ldif +++ b/conf/slapd/permission.ldif @@ -15,22 +15,22 @@ olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission' DESC 'YunoHost permission for user on permission side' SUP distinguishedName ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL' - DESC 'YunoHost permission main URL' + DESC 'YunoHost permission main URL' OBSOLETE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.5 NAME 'additionalUrls' - DESC 'YunoHost permission additionnal URL' + DESC 'YunoHost permission additionnal URL' OBSOLETE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.6 NAME 'authHeader' - DESC 'YunoHost application, enable authentication header' + DESC 'YunoHost application, enable authentication header' OBSOLETE SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.7 NAME 'label' - DESC 'YunoHost permission label, also used for the tile name in the SSO' + DESC 'YunoHost permission label, also used for the tile name in the SSO' OBSOLETE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} SINGLE-VALUE ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.8 NAME 'showTile' - DESC 'YunoHost application, show/hide the tile in the SSO for this permission' + DESC 'YunoHost application, show/hide the tile in the SSO for this permission' OBSOLETE SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: ( 1.3.6.1.4.1.17953.9.1.9 NAME 'isProtected' - DESC 'YunoHost application permission protection' + DESC 'YunoHost application permission protection' OBSOLETE SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) # OBJECTCLASS # For Applications @@ -41,8 +41,8 @@ olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh' olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh' DESC 'a YunoHost application' SUP top AUXILIARY - MUST ( cn $ authHeader $ label $ showTile $ isProtected ) - MAY ( groupPermission $ inheritPermission $ URL $ additionalUrls ) ) + MUST ( cn ) + MAY ( groupPermission $ inheritPermission $ URL $ additionalUrls $ authHeader $ label $ showTile $ isProtected ) ) # For User olcObjectClasses: ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh' DESC 'a YunoHost application' diff --git a/helpers/helpers.v1.d/permission b/helpers/helpers.v1.d/permission index 69853e634d..6c44423956 100644 --- a/helpers/helpers.v1.d/permission +++ b/helpers/helpers.v1.d/permission @@ -21,7 +21,7 @@ # Create a new permission for the app # # Example 1: `ynh_permission_create --permission=admin --url=/admin --additional_urls=domain.tld/admin /superadmin --allowed=alice bob \ -# --label="My app admin" --show_tile=true` +# --show_tile=true` # # This example will create a new permission permission with this following effect: # - A tile named "My app admin" in the SSO will be available for the users alice and bob. This tile will point to the relative url '/admin'. @@ -31,7 +31,7 @@ # Example 2: # # ynh_permission_create --permission=api --url=domain.tld/api --auth_header=false --allowed=visitors \ -# --label="MyApp API" --protected=true +# --protected=true # # This example will create a new protected permission. So the admin won't be able to add/remove the visitors group of this permission. # In case of an API with need to be always public it avoid that the admin break anything. @@ -43,14 +43,13 @@ # # # usage: ynh_permission_create --permission="permission" [--url="url"] [--additional_urls="second-url" [ "third-url" ]] [--auth_header=true|false] -# [--allowed=group1 [ group2 ]] [--label="label"] [--show_tile=true|false] +# [--allowed=group1 [ group2 ]] [--show_tile=true|false] # [--protected=true|false] # | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist) # | arg: -u, --url= - (optional) URL for which access will be allowed/forbidden. Note that if 'show_tile' is enabled, this URL will be the URL of the tile. # | arg: -A, --additional_urls= - (optional) List of additional URL for which access will be allowed/forbidden # | arg: -h, --auth_header= - (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application. Default is true # | arg: -a, --allowed= - (optional) A list of group/user to allow for the permission -# | arg: -l, --label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "APP_LABEL (permission name)". # | arg: -t, --show_tile= - (optional) Define if a tile will be shown in the SSO. If yes the name of the tile will be the 'label' parameter. Defaults to false for the permission different than 'main'. # | arg: -P, --protected= - (optional) Define if this permission is protected. If it is protected the administrator won't be able to add or remove the visitors group of this permission. Defaults to 'false'. # @@ -84,13 +83,12 @@ ynh_permission_create() { # Declare an array to define the options of this helper. local legacy_args=puAhaltP - local -A args_array=([p]=permission= [u]=url= [A]=additional_urls= [h]=auth_header= [a]=allowed= [l]=label= [t]=show_tile= [P]=protected=) + local -A args_array=([p]=permission= [u]=url= [A]=additional_urls= [h]=auth_header= [a]=allowed= [t]=show_tile= [P]=protected=) local permission local url local additional_urls local auth_header local allowed - local label local show_tile local protected ynh_handle_getopts_args "$@" @@ -98,7 +96,6 @@ ynh_permission_create() { additional_urls=${additional_urls:-} auth_header=${auth_header:-} allowed=${allowed:-} - label=${label:-} show_tile=${show_tile:-} protected=${protected:-} @@ -134,12 +131,6 @@ ynh_permission_create() { allowed=",allowed=['${allowed//;/\',\'}']" fi - if [[ -n ${label:-} ]]; then - label=",label='$label'" - else - label=",label='$permission'" - fi - if [[ -n ${show_tile:-} ]]; then if [ $show_tile == "true" ]; then show_tile=",show_tile=True" @@ -156,7 +147,7 @@ ynh_permission_create() { fi fi - yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' $url $additional_urls $auth_header $allowed $label $show_tile $protected)" + yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' $url $additional_urls $auth_header $allowed $show_tile $protected)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) @@ -266,11 +257,10 @@ ynh_permission_url() { # Update a permission for the app # # usage: ynh_permission_update --permission "permission" [--add="group" ["group" ...]] [--remove="group" ["group" ...]] -# [--label="label"] [--show_tile=true|false] [--protected=true|false] +# [--show_tile=true|false] [--protected=true|false] # | arg: -p, --permission= - the name for the permission (by default a permission named "main" already exist) # | arg: -a, --add= - the list of group or users to enable add to the permission # | arg: -r, --remove= - the list of group or users to remove from the permission -# | arg: -l, --label= - (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. # | arg: -t, --show_tile= - (optional) Define if a tile will be shown in the SSO # | arg: -P, --protected= - (optional) Define if this permission is protected. If it is protected the administrator won't be able to add or remove the visitors group of this permission. # @@ -278,17 +268,15 @@ ynh_permission_url() { ynh_permission_update() { # Declare an array to define the options of this helper. local legacy_args=parltP - local -A args_array=([p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected=) + local -A args_array=([p]=permission= [a]=add= [r]=remove= [t]=show_tile= [P]=protected=) local permission local add local remove - local label local show_tile local protected ynh_handle_getopts_args "$@" add=${add:-} remove=${remove:-} - label=${label:-} show_tile=${show_tile:-} protected=${protected:-} @@ -311,10 +299,6 @@ ynh_permission_update() { remove=",remove=['${remove//';'/"','"}']" fi - if [[ -n $label ]]; then - label=",label='$label'" - fi - if [[ -n $show_tile ]]; then if [ $show_tile == "true" ]; then show_tile=",show_tile=True" @@ -331,7 +315,7 @@ ynh_permission_update() { fi fi - yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission' $add $remove $label $show_tile $protected , force=True)" + yunohost tools shell -c "from yunohost.permission import user_permission_update; user_permission_update('$app.$permission' $add $remove $show_tile $protected , force=True)" } # Check if a permission has an user diff --git a/hooks/conf_regen/06-slapd b/hooks/conf_regen/06-slapd index 12e37f0058..b15a010d77 100755 --- a/hooks/conf_regen/06-slapd +++ b/hooks/conf_regen/06-slapd @@ -162,7 +162,10 @@ objectClass: top" nscd -i group fi - [ -z "$regen_conf_files" ] && exit 0 + if [ -z "$regen_conf_files" ] && [ $FORCE == "false" ] + then + exit 0 + fi # regenerate LDAP config directory from slapd.conf echo "Regenerate LDAP config directory from config.ldif" diff --git a/src/app.py b/src/app.py index f52c724cee..ab914c470a 100644 --- a/src/app.py +++ b/src/app.py @@ -131,15 +131,11 @@ def app_info(app, full=False, upgradable=False): setting_path = os.path.join(APPS_SETTING_PATH, app) local_manifest = _get_manifest_of_app(setting_path) - permissions = user_permission_list(full=True, absolute_urls=True, apps=[app])[ - "permissions" - ] - settings = _get_app_settings(app) ret = { "description": _value_for_locale(local_manifest["description"]), - "name": permissions.get(app + ".main", {}).get("label", local_manifest["name"]), + "name": settings.get("label", local_manifest["name"]), "version": local_manifest.get("version", "-"), } @@ -247,12 +243,13 @@ def app_info(app, full=False, upgradable=False): and local_manifest["resources"].get("data_dir") is not None ) - ret["permissions"] = permissions - ret["label"] = permissions.get(app + ".main", {}).get("label") + ret["permissions"] = user_permission_list(full=True, absolute_urls=True, apps=[app])[ + "permissions" + ] + + # FIXME : this is the same stuff as "name" ... maybe we should get rid of "name" ? + ret["label"] = settings.get("label", local_manifest["name"]) - if not ret["label"]: - logger.debug(f"Failed to get label for app {app}, maybe it is not a webapp?") - ret["label"] = local_manifest["name"] return ret @@ -1149,6 +1146,10 @@ def app_install( shutil.rmtree(app_setting_path) os.makedirs(app_setting_path) + # Hotfix for bug in the webadmin while we fix the actual issue :D + if label == "undefined": + label = None + # Set initial app settings app_settings = { "id": app_instance_name, @@ -1156,6 +1157,9 @@ def app_install( "current_revision": manifest.get("remote", {}).get("revision", "?"), } + if label: + app_settings["label"] = label + # If packaging_format v2+, save all install options as settings if packaging_format >= 2: for option in options: @@ -1180,15 +1184,6 @@ def app_install( recursive=True, ) - # Hotfix for bug in the webadmin while we fix the actual issue :D - if label == "undefined": - label = None - - # Override manifest name by given label - # This info is also later picked-up by the 'permission' resource initialization - if label: - manifest["name"] = label - if packaging_format >= 2: from yunohost.utils.resources import AppResourceManager @@ -1210,7 +1205,6 @@ def app_install( permission_create( app_instance_name + ".main", allowed=["all_users"], - label=manifest["name"], show_tile=False, protected=False, ) @@ -1686,10 +1680,11 @@ def app_ssowatconf(): # New permission system for perm_name, perm_info in all_permissions.items(): + uris = ( [] - + ([perm_info["url"]] if perm_info["url"] else []) - + perm_info["additional_urls"] + + ([perm_info["url"]] if perm_info.get("url") else []) + + perm_info.get("additional_urls", []) ) # Ignore permissions for which there's no url defined @@ -1814,19 +1809,14 @@ def app_ssowatconf(): def app_change_label(app, new_label): - from yunohost.permission import user_permission_update installed = _is_installed(app) if not installed: raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) - logger.warning(m18n.n("app_label_deprecated")) - user_permission_update(app + ".main", label=new_label) - -# actions todo list: -# * docstring + app_setting(app, "label", new_label) def app_action_list(app): diff --git a/src/backup.py b/src/backup.py index 27268d3c5a..b56b1a160b 100644 --- a/src/backup.py +++ b/src/backup.py @@ -1297,7 +1297,7 @@ def _restore_system(self): # Restore permission for apps installed for permission_name, permission_infos in old_apps_permission.items(): - app_name, perm_name = permission_name.split(".") + app_name, _ = permission_name.split(".") if _is_installed(app_name): permission_create( permission_name, @@ -1305,11 +1305,6 @@ def _restore_system(self): url=permission_infos["url"], additional_urls=permission_infos["additional_urls"], auth_header=permission_infos["auth_header"], - label=( - permission_infos["label"] - if perm_name == "main" - else permission_infos["sublabel"] - ), show_tile=permission_infos["show_tile"], protected=permission_infos["protected"], sync_perm=False, @@ -1429,18 +1424,12 @@ def copytree(src, dst, symlinks=False, ignore=None): g for g in permission_infos["allowed"] if g in existing_groups ] - perm_name = permission_name.split(".")[1] permission_create( permission_name, allowed=should_be_allowed, url=permission_infos.get("url"), additional_urls=permission_infos.get("additional_urls"), auth_header=permission_infos.get("auth_header"), - label=( - permission_infos.get("label") - if perm_name == "main" - else permission_infos.get("sublabel") - ), show_tile=permission_infos.get("show_tile", True), protected=permission_infos.get("protected", False), sync_perm=False, diff --git a/src/migrations/0032_rework_permission_infos.py b/src/migrations/0032_rework_permission_infos.py new file mode 100644 index 0000000000..894099c5d5 --- /dev/null +++ b/src/migrations/0032_rework_permission_infos.py @@ -0,0 +1,87 @@ +from logging import getLogger + +from yunohost.tools import Migration +from yunohost.regenconf import regen_conf +from yunohost.permission import permission_sync_to_user +from yunohost.app import app_setting + +logger = getLogger("yunohost.migration") + +################################################### +# Tools used also for restoration +################################################### + + +class MyMigration(Migration): + + introduced_in_version = "12.1" + dependencies = [] + + ldap_migration_started = False + + @Migration.ldap_migration + def run(self, *args): + + regen_conf(["slapd"], force=True) + + self.ldap_migration_started = True + + permissions_per_app = self.read_legacy_permissions_per_app() + for app, permissions in permissions_per_app.items(): + app_setting(app, "_permissions", permissions) + + permission_sync_to_user() + + def run_after_system_restore(self): + self.run() + + def read_legacy_permissions_per_app(self): + + from yunohost.utils.ldap import _get_ldap_interface + SYSTEM_PERMS = ["mail", "sftp", "ssh"] + + ldap = _get_ldap_interface() + permissions_infos = ldap.search( + "ou=permission", + "(objectclass=permissionYnh)", + [ + "cn", + "URL", + "additionalUrls", + "authHeader", + "label", + "showTile", + "isProtected", + ], + ) + + permissions_per_app = {} + for infos in permissions_infos: + app, name = infos["cn"][0].split(".") + + # LDAP won't delete the old, obsolete info, we have to do it ourselves ~_~ + ldap.update(f'cn={infos["cn"][0]},ou=permission', { + 'label': [], + 'authHeader': [], + 'showTile': [], + 'isProtected': [], + 'URL': [], + 'additionalUrls': [] + }) + + if app in SYSTEM_PERMS: + continue + + if app not in permissions_per_app: + permissions_per_app[app] = {} + + permissions_per_app[app][name] = { + "label": infos.get("label", [None])[0], + "show_tile": infos.get("showTile", [False])[0] == "TRUE", + "auth_header": infos.get("authHeader", [False])[0] == "TRUE", + "protected": infos.get("isProtected", [False])[0] == "TRUE", + "url": infos.get("URL", [None])[0], + "additional_urls": infos.get("additionalUrls", []), + } + + return permissions_per_app diff --git a/src/permission.py b/src/permission.py index 7dc00872b6..1afdde0c3b 100644 --- a/src/permission.py +++ b/src/permission.py @@ -31,7 +31,11 @@ logger = getLogger("yunohost.user") -SYSTEM_PERMS = ["mail", "sftp", "ssh"] +SYSTEM_PERMS = { + "mail": "Email", + "sftp": "SFTP", + "ssh": "SSH", +} # # @@ -52,19 +56,13 @@ def user_permission_list( from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract ldap = _get_ldap_interface() - permissions_infos = ldap.search( + ldap_permissions_infos = ldap.search( "ou=permission", "(objectclass=permissionYnh)", [ "cn", "groupPermission", "inheritPermission", - "URL", - "additionalUrls", - "authHeader", - "label", - "showTile", - "isProtected", ], ) @@ -81,9 +79,9 @@ def user_permission_list( } permissions = {} - for infos in permissions_infos: + for infos in ldap_permissions_infos: name = infos["cn"][0] - app = name.split(".")[0] + app, subperm = name.split(".") if ignore_system_perms and app in SYSTEM_PERMS: continue @@ -91,20 +89,25 @@ def user_permission_list( continue perm = {} - perm["allowed"] = [ - _ldap_path_extract(p, "cn") for p in infos.get("groupPermission", []) - ] - - if full: - perm["corresponding_users"] = [ - _ldap_path_extract(p, "uid") for p in infos.get("inheritPermission", []) - ] - perm["auth_header"] = infos.get("authHeader", [False])[0] == "TRUE" - perm["label"] = infos.get("label", [None])[0] - perm["show_tile"] = infos.get("showTile", [False])[0] == "TRUE" - perm["protected"] = infos.get("isProtected", [False])[0] == "TRUE" - perm["url"] = infos.get("URL", [None])[0] - perm["additional_urls"] = infos.get("additionalUrls", []) + if full and app not in SYSTEM_PERMS and app in installed_apps: + + # Default stuff + perm = { + "url": None, + "additional_urls": [], + "auth_header": True, + "show_tile": None, # To be automagically set to True by default if an url is defined and show_tile not provided + "protected": False, + } + perm_settings = (app_setting(app, "_permissions") or {}).get(subperm, {}) + perm.update(perm_settings) + + perm["label"] = app_setting(app, "label") or app.title() + if subperm != "main": + perm["label"] += " (" + perm_settings.get("label", subperm) + ")" + + if perm["show_tile"] is None and perm["url"] is not None: + perm["show_tile"] = True if absolute_urls: app_base_path = ( @@ -115,25 +118,20 @@ def user_permission_list( _get_absolute_url(url, app_base_path) for url in perm["additional_urls"] ] + elif full and app in SYSTEM_PERMS: + perm["label"] = SYSTEM_PERMS[app] + if subperm != "main": + perm["label"] += f" ({subperm})" - permissions[name] = perm + perm["allowed"] = [ + _ldap_path_extract(p, "cn") for p in infos.get("groupPermission", []) + ] + if full: + perm["corresponding_users"] = [ + _ldap_path_extract(p, "uid") for p in infos.get("inheritPermission", []) + ] - # Make sure labels for sub-permissions are the form " Applabel (Sublabel) " - if full: - subpermissions = { - k: v for k, v in permissions.items() if not k.endswith(".main") - } - for name, infos in subpermissions.items(): - main_perm_name = name.split(".")[0] + ".main" - if main_perm_name not in permissions: - logger.debug( - f"Uhoh, unknown permission {main_perm_name} ? (Maybe we're in the process or deleting the perm for this app...)" - ) - continue - main_perm_label = permissions[main_perm_name]["label"] - infos["sublabel"] = infos["label"] - label_ = infos["label"] - infos["label"] = f"{main_perm_label} ({label_})" + permissions[name] = perm if short: permissions = list(permissions.keys()) @@ -271,12 +269,16 @@ def user_permission_update( # Commit the new allowed group list operation_logger.start() - new_permission = _update_ldap_group_permission( + _update_app_permission_setting( permission=permission, - allowed=new_allowed_groups, label=label, show_tile=show_tile, protected=protected, + ) + + new_permission = _update_ldap_group_permission( + permission=permission, + allowed=new_allowed_groups, sync_perm=sync_perm, ) @@ -360,7 +362,6 @@ def permission_create( url=None, additional_urls=None, auth_header=True, - label=None, show_tile=False, protected=False, sync_perm=True, @@ -374,7 +375,6 @@ def permission_create( url -- (optional) URL for which access will be allowed/forbidden additional_urls -- (optional) List of additional URL for which access will be allowed/forbidden auth_header -- (optional) Define for the URL of this permission, if SSOwat pass the authentication header to the application - label -- (optional) Define a name for the permission. This label will be shown on the SSO and in the admin. Default is "permission name" show_tile -- (optional) Define if a tile will be shown in the SSO protected -- (optional) Define if the permission can be added/removed to the visitor group @@ -390,6 +390,7 @@ def permission_create( re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$ """ + from yunohost.app import _is_installed from yunohost.user import user_group_list from yunohost.utils.ldap import _get_ldap_interface @@ -417,16 +418,6 @@ def permission_create( "objectClass": ["top", "permissionYnh", "posixGroup"], "cn": str(permission), "gidNumber": gid, - "authHeader": ["TRUE"], - "label": [ - str(label) if label else (subperm if subperm != "main" else app.title()) - ], - "showTile": [ - "FALSE" - ], # Dummy value, it will be fixed when we call '_update_ldap_group_permission' - "isProtected": [ - "FALSE" - ], # Dummy value, it will be fixed when we call '_update_ldap_group_permission' } if allowed is not None: @@ -450,22 +441,27 @@ def permission_create( ) try: - permission_url( - permission, - url=url, - add_url=additional_urls, - auth_header=auth_header, - sync_perm=False, - ) + if _is_installed(app): + permission_url( + permission, + url=url, + add_url=additional_urls, + auth_header=auth_header, + sync_perm=False, + ) + + _update_app_permission_setting( + permission=permission, + show_tile=show_tile, + protected=protected, + ) new_permission = _update_ldap_group_permission( permission=permission, - allowed=allowed, - label=label, - show_tile=show_tile, - protected=protected, + allowed=allowed or [], sync_perm=sync_perm, ) + except Exception: permission_delete(permission, force=True) raise @@ -499,15 +495,15 @@ def permission_url( clear_urls -- (optional) Clean all urls (url and additional_urls) """ from yunohost.app import app_setting - from yunohost.utils.ldap import _get_ldap_interface - - ldap = _get_ldap_interface() # By default, manipulate main permission if "." not in permission: permission = permission + ".main" - app = permission.split(".")[0] + app, sub_permission = permission.split(".") + + if app in SYSTEM_PERMS: + logger.warning(f"Cannot change urls / auth_header for system perm {permission}") if url or add_url: domain = app_setting(app, "domain") @@ -518,23 +514,23 @@ def permission_url( app_main_path = domain + path # Fetch existing permission + update_settings = {} + existing_permission = app_setting(app, "_permissions") or {} + if sub_permission not in existing_permission: + existing_permission[sub_permission] = {} + existing_permission = existing_permission[sub_permission] - existing_permission = user_permission_info(permission) - - show_tile = existing_permission["show_tile"] - - if url is None: - url = existing_permission["url"] - else: + if url is not None: url = _validate_and_sanitize_permission_url(url, app_main_path, app) + update_settings["url"] = url - if url.startswith("re:") and existing_permission["show_tile"]: + if url.startswith("re:") and existing_permission.get("show_tile"): logger.warning( m18n.n("regex_incompatible_with_tile", regex=url, permission=permission) ) - show_tile = False + update_settings["show_tile"] = False - current_additional_urls = existing_permission["additional_urls"] + current_additional_urls = existing_permission.get("additional_urls", []) new_additional_urls = copy.copy(current_additional_urls) if add_url: @@ -563,32 +559,28 @@ def permission_url( if set_url: new_additional_urls = set_url - if auth_header is None: - auth_header = existing_permission["auth_header"] + # Guarantee uniqueness of all values, which would otherwise make ldap.update angry. + update_settings["additional_urls"] = list(set(new_additional_urls)) - if clear_urls: - url = None - new_additional_urls = [] - show_tile = False + if auth_header is not None: + update_settings["auth_header"] = auth_header - # Guarantee uniqueness of all values, which would otherwise make ldap.update angry. - new_additional_urls = set(new_additional_urls) + if clear_urls: + update_settings["url"] = None + update_settings["additional_urls"] = [] + update_settings["show_tile"] = False # Actually commit the change - - operation_logger.related_to.append(("app", permission.split(".")[0])) + operation_logger.related_to.append(("app", app)) operation_logger.start() try: - ldap.update( - f"cn={permission},ou=permission", - { - "URL": [url] if url is not None else [], - "additionalUrls": new_additional_urls, - "authHeader": [str(auth_header).upper()], - "showTile": [str(show_tile).upper()], - }, - ) + perm_settings = app_setting(app, "_permissions") or {} + if sub_permission not in perm_settings: + perm_settings[sub_permission] = {} + + perm_settings[sub_permission].update(update_settings) + app_setting(app, "_permissions", perm_settings) except Exception as e: raise YunohostError("permission_update_failed", permission=permission, error=e) @@ -616,6 +608,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) raise YunohostValidationError("permission_cannot_remove_main") from yunohost.utils.ldap import _get_ldap_interface + from yunohost.app import app_setting, _is_installed ldap = _get_ldap_interface() @@ -635,6 +628,13 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) "permission_deletion_failed", permission=permission, error=e ) + app, subperm = permission.split(".") + if _is_installed(app): + perm_settings = app_setting(app, "_permissions") or {} + if subperm in perm_settings: + del perm_settings[subperm] + app_setting(app, "_permissions", perm_settings) + if sync_perm: permission_sync_to_user() logger.debug(m18n.n("permission_deleted", permission=permission)) @@ -697,69 +697,108 @@ def permission_sync_to_user(): os.system("nscd --invalidate=group") -def _update_ldap_group_permission( - permission, allowed, label=None, show_tile=None, protected=None, sync_perm=True -): +def _update_app_permission_setting(permission, label=None, show_tile=None, protected=None): """ - Internal function that will rewrite user permission - - permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) - allowed -- (optional) A list of group/user to allow for the permission label -- (optional) Define a name for the permission. This label will be shown on the SSO and in the admin show_tile -- (optional) Define if a tile will be shown in the SSO protected -- (optional) Define if the permission can be added/removed to the visitor group - - - Assumptions made, that should be checked before calling this function: - - the permission does currently exists ... - - the 'allowed' list argument is *different* from the current - permission state ... otherwise ldap will miserably fail in such - case... - - the 'allowed' list contains *existing* groups. """ - from yunohost.hook import hook_callback - from yunohost.utils.ldap import _get_ldap_interface - - ldap = _get_ldap_interface() - - existing_permission = user_permission_info(permission) + from yunohost.app import app_setting - update = {} + app, sub_permission = permission.split(".") + update_settings = {} + perm_settings = app_setting(app, "_permissions") or {} + if sub_permission not in perm_settings: + perm_settings[sub_permission] = {} - if allowed is not None: - allowed = [allowed] if not isinstance(allowed, list) else allowed - # Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry. - allowed = set(allowed) - update["groupPermission"] = [ - "cn=" + g + ",ou=groups,dc=yunohost,dc=org" for g in allowed - ] + if app in SYSTEM_PERMS: + logger.warning(f"Can't change label / show_tile / protected for system permission {permission}") + return if label is not None: - update["label"] = [str(label)] + update_settings["label"] = str(label) if protected is not None: - update["isProtected"] = [str(protected).upper()] + update_settings["protected"] = protected if show_tile is not None: + update_settings["show_tile"] = show_tile + existing_permission_url = perm_settings[sub_permission].get("url") if show_tile is True: - if not existing_permission["url"]: + if not existing_permission_url: logger.warning( m18n.n( "show_tile_cant_be_enabled_for_url_not_defined", permission=permission, ) ) - show_tile = False - elif existing_permission["url"].startswith("re:"): + update_settings["show_tile"] = False + elif existing_permission_url.startswith("re:"): logger.warning( m18n.n("show_tile_cant_be_enabled_for_regex", permission=permission) ) - show_tile = False - update["showTile"] = [str(show_tile).upper()] + update_settings["show_tile"] = False + + if "label" in update_settings and sub_permission == "main": + label = update_settings.pop("label") + app_setting(app, "label", label) + + perm_settings[sub_permission].update(update_settings) + app_setting(app, "_permissions", perm_settings) + + +def _update_ldap_group_permission(permission, allowed, sync_perm=True): + """ + Internal function that will rewrite user permission + + permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors) + allowed -- (optional) A list of group/user to allow for the permission + + Assumptions made, that should be checked before calling this function: + - the permission does currently exists ... + - the 'allowed' list argument is *different* from the current + permission state ... otherwise ldap will miserably fail in such + case... + - the 'allowed' list contains *existing* groups. + """ + + from yunohost.hook import hook_callback + from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract + + ldap = _get_ldap_interface() + app, sub_permission = permission.split(".") + update_ldap = {} + + # NB: this must be fetched *BEFORE* we modifiy the perm + existing_permission = ldap.search( + "ou=permission", + f"(&(objectclass=permissionYnh)(cn={permission}))", + [ + "cn", + "groupPermission", + "inheritPermission", + ], + ) + if existing_permission: + existing_permission = existing_permission[0] + else: + logger.warning(f"Trying to update unknown permission {permission}") + return {} + + existing_permission["allowed"] = [_ldap_path_extract(p, "cn") for p in existing_permission.get("groupPermission", [])] + existing_permission["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in existing_permission.get("inheritPermission", [])] + + assert isinstance(allowed, list) or isinstance(allowed, str) + allowed = [allowed] if not isinstance(allowed, list) else allowed + # Guarantee uniqueness of values in allowed, which would otherwise make ldap.update angry. + allowed = set(allowed) + update_ldap["groupPermission"] = [ + f"cn={g},ou=groups,dc=yunohost,dc=org" for g in allowed + ] try: - ldap.update(f"cn={permission},ou=permission", update) + ldap.update(f"cn={permission},ou=permission", update_ldap) except Exception as e: raise YunohostError("permission_update_failed", permission=permission, error=e) @@ -772,9 +811,6 @@ def _update_ldap_group_permission( # Trigger app callbacks - app = permission.split(".")[0] - sub_permission = permission.split(".")[1] - old_corresponding_users = set(existing_permission["corresponding_users"]) new_corresponding_users = set(new_permission["corresponding_users"]) diff --git a/src/regenconf.py b/src/regenconf.py index 49df33bf14..cba3219614 100644 --- a/src/regenconf.py +++ b/src/regenconf.py @@ -149,6 +149,7 @@ def _pre_call(name, priority, path, args): # so that scripts dont have to call 'yunohost settings get' manually # which is painful performance-wise env["YNH_SETTINGS"] = json.dumps(settings_get("", export=True)) + env["FORCE"] = "true" if force else "false" pre_result = hook_callback("conf_regen", names, pre_callback=_pre_call, env=env) diff --git a/src/tests/test_permission.py b/src/tests/test_permission.py index 09ffe31a30..2094fd97ee 100644 --- a/src/tests/test_permission.py +++ b/src/tests/test_permission.py @@ -74,7 +74,6 @@ def _permission_create_with_dummy_app( url=None, additional_urls=None, auth_header=True, - label=None, show_tile=False, protected=True, sync_perm=True, @@ -109,7 +108,6 @@ def _permission_create_with_dummy_app( url=url, additional_urls=additional_urls, auth_header=auth_header, - label=label, show_tile=show_tile, protected=protected, sync_perm=sync_perm, @@ -186,7 +184,6 @@ def new_getaddrinfo(*args): url="/", additional_urls=["/whatever", "/idontnow"], auth_header=False, - label="Wiki", show_tile=True, allowed=["all_users"], protected=False, @@ -465,7 +462,7 @@ def test_permission_list(): def test_permission_create_main(): with message("permission_created", permission="site.main"): - permission_create("site.main", allowed=["all_users"], protected=False) + _permission_create_with_dummy_app("site.main", allowed=["all_users"], protected=False) res = user_permission_list(full=True)["permissions"] assert "site.main" in res @@ -476,7 +473,7 @@ def test_permission_create_main(): def test_permission_create_extra(): with message("permission_created", permission="site.test"): - permission_create("site.test") + _permission_create_with_dummy_app("site.test", protected=None) res = user_permission_list(full=True)["permissions"] assert "site.test" in res @@ -487,7 +484,8 @@ def test_permission_create_extra(): def test_permission_create_with_specific_user(): - permission_create("site.test", allowed=["alice"]) + with message("permission_created", permission="site.test"): + _permission_create_with_dummy_app("site.test", allowed=["alice"]) res = user_permission_list(full=True)["permissions"] assert "site.test" in res @@ -499,7 +497,6 @@ def test_permission_create_with_tile_management(): _permission_create_with_dummy_app( "site.main", allowed=["all_users"], - label="The Site", show_tile=False, domain=maindomain, path="/site", @@ -507,7 +504,7 @@ def test_permission_create_with_tile_management(): res = user_permission_list(full=True)["permissions"] assert "site.main" in res - assert res["site.main"]["label"] == "The Site" + assert res["site.main"]["label"] == "Site" assert res["site.main"]["show_tile"] is False @@ -979,7 +976,6 @@ def test_show_tile_cant_be_enabled(): _permission_create_with_dummy_app( permission="site.main", auth_header=False, - label="Site", show_tile=True, allowed=["all_users"], protected=False, @@ -992,7 +988,6 @@ def test_show_tile_cant_be_enabled(): permission="web.main", url="re:/[a-z]{3}/bla", auth_header=False, - label="Web", show_tile=True, allowed=["all_users"], protected=False, diff --git a/src/tools.py b/src/tools.py index e3e6a3c3ee..e636fd78c1 100644 --- a/src/tools.py +++ b/src/tools.py @@ -301,7 +301,6 @@ def tools_regen_conf( ): from yunohost.regenconf import regen_conf - return regen_conf(names, with_diff, force, dry_run, list_pending) diff --git a/src/utils/resources.py b/src/utils/resources.py index ef96f2e795..4b378baabf 100644 --- a/src/utils/resources.py +++ b/src/utils/resources.py @@ -146,7 +146,7 @@ class AppResource: def __init__(self, properties: Dict[str, Any], app: str, manager=None): self.app = app - self.manager = manager + self.workdir = manager.workdir if manager else None properties = self.default_properties | properties # It's not guaranteed that this info will be defined, e.g. during unit tests, only small resource snippets are used, not proper manifests @@ -255,11 +255,7 @@ def _run_script(self, action, script, env={}): ) from yunohost.hook import hook_exec_with_script_debug_if_failure - workdir = ( - self.manager.workdir - if self.manager and self.manager.workdir - else _make_tmp_workdir_for_app(app=self.app) - ) + workdir = self.workdir or _make_tmp_workdir_for_app(app=self.app) env_ = _make_environment_for_app_script( self.app, @@ -725,8 +721,6 @@ def provision_or_update(self, context: Dict = {}): permission_create( perm_id, allowed=init_allowed, - # This is why the ugly hack with self.manager exists >_> - label=self.manager.wanted["name"] if perm == "main" else perm, url=infos["url"], additional_urls=infos["additional_urls"], auth_header=infos["auth_header"],