Merge pull request #797 from YunoHost/permission-visitors

'visitors' mechanics (replace the old public/private mechanic) + integrate the permission system with SSOwat
This commit is contained in:
Alexandre Aubin 2019-10-15 23:24:46 +02:00 committed by GitHub
commit 55aff6aaa1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 459 additions and 207 deletions

View file

@ -176,6 +176,8 @@ else:
elif action == "set":
if key in ['redirected_urls', 'redirected_regex']:
value = yaml.load(value)
if key in ["unprotected_uris", "unprotected_regex", "protected_uris", "protected_regex"]:
logger.warning("/!\\ Packagers! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to manage public/private access.")
settings[key] = value
else:
raise ValueError("action should either be get, set or delete")
@ -230,29 +232,51 @@ ynh_webpath_register () {
# Create a new permission for the app
#
# usage: ynh_permission_create --permission "permission" [--urls "url" ["url" ...]]
# | arg: permission - the name for the permission (by default a permission named "main" already exist)
# | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin)
# example: ynh_permission_create --permission admin --url /admin --allowed alice bob
#
# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2]
# | arg: permission - the name for the permission (by default a permission named "main" already exist)
# | arg: url - (optional) URL for which access will be allowed/forbidden
# | arg: allowed - (optional) A list of group/user to allow for the permission
#
# If provided, 'url' is assumed to be relative to the app domain/path if they
# start with '/'. For example:
# / -> domain.tld/app
# /admin -> domain.tld/app/admin
# domain.tld/app/api -> domain.tld/app/api
#
# 'url' can be later treated as a regex if it starts with "re:".
# For example:
# re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
# re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
#
# example: ynh_permission_create --permission admin --urls domain.tld/blog/admin
ynh_permission_create() {
declare -Ar args_array=( [p]=permission= [u]=urls= )
declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= )
local permission
local urls
local url
local allowed
ynh_handle_getopts_args "$@"
if [[ -n ${urls:-} ]]; then
urls=",urls=['${urls//';'/"','"}']"
if [[ -n ${url:-} ]]; then
url="'$url'"
else
url="None"
fi
yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' ${urls:-}, sync_perm=False)"
if [[ -n ${allowed:-} ]]; then
allowed=",allowed=['${allowed//';'/"','"}']"
fi
yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , 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 --permission "permission"
# example: ynh_permission_delete --permission editors
#
# usage: ynh_permission_delete --permission "permission"
# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
#
# example: ynh_permission_delete --permission editors
ynh_permission_delete() {
declare -Ar args_array=( [p]=permission= )
local permission
@ -261,30 +285,28 @@ ynh_permission_delete() {
yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)"
}
# Manage urls related to a permission
# Redefine the url associated to a permission
#
# usage: ynh_permission_urls --permission "permission" --add "url" ["url" ...] --remove "url" ["url" ...]
# usage: ynh_permission_url --permission "permission" --url "url"
# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
# | arg: add - (optional) a list of FULL urls to add to the permission (e.g. domain.tld/apps/admin)
# | arg: remove - (optional) a list of FULL urls to remove from the permission (e.g. other.tld/apps/admin)
# | arg: url - (optional) URL for which access will be allowed/forbidden
#
ynh_permission_urls() {
declare -Ar args_array=([p]=permission= [a]=add= [r]=remove=)
ynh_permission_url() {
declare -Ar args_array=([p]=permission= [u]=url=)
local permission
local add
local remove
local url
ynh_handle_getopts_args "$@"
if [[ -n ${add:-} ]]; then
add=",add=['${add//';'/"','"}']"
fi
if [[ -n ${remove:-} ]]; then
remove=",remove=['${remove//';'/"','"}']"
if [[ -n ${url:-} ]]; then
url="'$url'"
else
url="None"
fi
yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission' ${add:-} ${remove:-})"
yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission', url=$url)"
}
# Update a permission for the app
#
# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...]

View file

@ -57,6 +57,12 @@ children:
objectClass:
- posixGroup
- groupOfNamesYnh
cn=visitors,ou=groups:
cn: visitors
gidNumber: "4003"
objectClass:
- posixGroup
- groupOfNamesYnh
depends_children:
cn=mail.main,ou=permission:

View file

@ -222,6 +222,9 @@
"group_already_exist_on_system": "Group {group} already exists in the system groups",
"group_created": "Group '{group}' created",
"group_creation_failed": "Could not create the group '{group}': {error}",
"group_cannot_edit_all_users": "The group 'all_users' cannot be edited manually. It is a special group meant to contain all users registered in YunoHost",
"group_cannot_edit_visitors": "The group 'visitors' cannot be edited manually. It is a special group representing anonymous visitors",
"group_cannot_edit_primary_group": "The group '{group}' cannot be edited manually. It is the primary group meant to contain only one specific user.",
"group_cannot_be_edited": "The group {group} cannot be edited manually.",
"group_cannot_be_deleted": "The group {group} cannot be deleted manually.",
"group_deleted": "Group '{group}' deleted",
@ -267,7 +270,7 @@
"log_letsencrypt_cert_install": "Install a Let's encrypt certificate on '{}' domain",
"log_permission_create": "Create permission '{}'",
"log_permission_delete": "Delete permission '{}'",
"log_permission_urls": "Update urls related to permission '{}'",
"log_permission_url": "Update url related to permission '{}'",
"log_selfsigned_cert_install": "Install self signed certificate on '{}' domain",
"log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate",
"log_regen_conf": "Regenerate system configurations '{}'",
@ -345,7 +348,7 @@
"migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}",
"migration_0011_create_group": "Creating a group for each user…",
"migration_0011_done": "Migration successful. You are now able to manage usergroups.",
"migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration needs to be updated.\nYou need to save your current configuration, reintialize the original configuration by running 'yunohost tools regen-conf -f' and retry the migration",
"migration_0011_slapd_config_will_be_overwritten": "It looks like you manually edited the slapd configuration. For this critical migration, YunoHost needs to force the update of the slapd configuration. The original files will be backuped in {conf_backup_folder}.",
"migration_0011_LDAP_update_failed": "Could not update LDAP. Error: {error:s}",
"migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP…",
"migration_0011_migration_failed_trying_to_rollback": "Migration failed… trying to roll back the system.",
@ -414,9 +417,12 @@
"permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled'",
"permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'",
"permission_already_exist": "Permission '{permission}' already exists",
"permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.",
"permission_cannot_remove_main": "Removing a main permission is not allowed",
"permission_created": "Permission '{permission:s}' created",
"permission_creation_failed": "Could not create permission '{permission}': {error}",
"permission_currently_allowed_for_visitors": "This permission is currently granted to visitors in addition to other groups. You probably want to either remove the 'visitors' permission or remove the other groups it is currently granted to.",
"permission_currently_allowed_for_all_users": "This permission is currently granted to all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the other groups it is currently granted to.",
"permission_deleted": "Permission '{permission:s}' deleted",
"permission_deletion_failed": "Could not delete permission '{permission}': {error}",
"permission_not_found": "Permission '{permission:s}' not found",

View file

@ -41,7 +41,8 @@ from datetime import datetime
from moulinette import msignals, m18n, msettings
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_json, read_toml, write_to_json
from moulinette.utils.filesystem import read_json, read_toml, read_yaml, write_to_json
from moulinette.utils.filesystem import read_json, read_toml
from yunohost.service import service_log, service_status, _run_service_command
from yunohost.utils import packages
@ -427,23 +428,85 @@ def app_map(app=None, raw=False, user=None):
if 'path' not in app_settings:
# we assume that an app that doesn't have a path doesn't have an HTTP api
continue
# This 'no_sso' settings sound redundant to not having $path defined ....
# At least from what I can see, all apps using it don't have a path defined ...
if 'no_sso' in app_settings: # I don't think we need to check for the value here
continue
if user and user not in permissions[app_id + ".main"]["corresponding_users"]:
continue
# Users must at least have access to the main permission to have access to extra permissions
if user:
if not app_id + ".main" in permissions:
logger.warning("Uhoh, no main permission was found for app %s ... sounds like an app was only partially removed due to another bug :/" % app_id)
continue
main_perm = permissions[app_id + ".main"]
if user not in main_perm["corresponding_users"] and "visitors" not in main_perm["allowed"]:
continue
domain = app_settings['domain']
path = app_settings['path']
path = app_settings['path'].rstrip('/')
label = app_settings['label']
if raw:
if domain not in result:
result[domain] = {}
result[domain][path] = {
'label': app_settings['label'],
'id': app_settings['id']
}
else:
result[domain + path] = app_settings['label']
def _sanitized_absolute_url(perm_url):
# Nominal case : url is relative to the app's path
if perm_url.startswith("/"):
perm_domain = domain
perm_path = path + perm_url.rstrip("/")
# Otherwise, the urls starts with a domain name, like domain.tld/foo/bar
# We want perm_domain = domain.tld and perm_path = "/foo/bar"
else:
perm_domain, perm_path = perm_url.split("/", 1)
perm_path = "/" + perm_path.rstrip("/")
return perm_domain, perm_path
this_app_perms = {p: i for p, i in permissions.items() if p.startswith(app_id + ".") and i["url"]}
for perm_name, perm_info in this_app_perms.items():
# If we're building the map for a specific user, check the user
# actually is allowed for this specific perm
if user and user not in perm_info["corresponding_users"] and "visitors" not in perm_info["allowed"]:
continue
if perm_info["url"].startswith("re:"):
# Here, we have an issue if the chosen url is a regex, because
# the url we want to add to the dict is going to be turned into
# a clickable link (or analyzed by other parts of yunohost
# code...). To put it otherwise : in the current code of ssowat,
# you can't give access a user to a regex.
#
# Instead, as drafted by Josue, we could rework the ssowat logic
# about how routes and their permissions are defined. So for example,
# have a dict of
# { "/route1": ["visitors", "user1", "user2", ...], # Public route
# "/route2_with_a_regex$": ["user1", "user2"], # Private route
# "/route3": None, # Skipped route idk
# }
# then each time a user try to request and url, we only keep the
# longest matching rule and check the user is allowed etc...
#
# The challenge with this is (beside actually implementing it)
# is that it creates a whole new mechanism that ultimately
# replace all the existing logic about
# protected/unprotected/skipped uris and regexes and we gotta
# handle / migrate all the legacy stuff somehow if we don't
# want to end up with a total mess in the future idk
logger.error("Permission %s can't be added to the SSOwat configuration because it doesn't support regexes so far..." % perm_name)
continue
perm_domain, perm_path = _sanitized_absolute_url(perm_info["url"])
if perm_name.endswith(".main"):
perm_label = label
else:
# e.g. if perm_name is wordpress.admin, we want "Blog (Admin)" (where Blog is the label of this app)
perm_label = "%s (%s)" % (label, perm_name.rsplit(".")[-1].replace("_", " ").title())
if raw:
if domain not in result:
result[perm_domain] = {}
result[perm_domain][perm_path] = {
'label': perm_label,
'id': app_id
}
else:
result[perm_domain + perm_path] = perm_label
return result
@ -461,7 +524,6 @@ def app_change_url(operation_logger, app, domain, path):
"""
from yunohost.hook import hook_exec, hook_callback
from yunohost.domain import _normalize_domain_path, _get_conflicting_apps
from yunohost.permission import permission_urls
installed = _is_installed(app)
if not installed:
@ -551,8 +613,6 @@ def app_change_url(operation_logger, app, domain, path):
app_setting(app, 'domain', value=domain)
app_setting(app, 'path', value=path)
permission_urls(app+".main", add=[domain+path], remove=[old_domain+old_path], sync_perm=True)
# avoid common mistakes
if _run_service_command("reload", "nginx") is False:
# grab nginx errors
@ -763,7 +823,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
from yunohost.log import OperationLogger
from yunohost.permission import user_permission_list, permission_create, permission_urls, permission_delete, permission_sync_to_user
from yunohost.permission import user_permission_list, permission_create, permission_url, permission_delete, permission_sync_to_user, user_permission_update
# Fetch or extract sources
if not os.path.exists(INSTALL_TMP):
@ -920,10 +980,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
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)
# Note that sync_perm is disabled to avoid triggering a whole bunch of code and messages
# can't be sure that we don't have one case when it's needed
permission_create(app_instance_name+".main", sync_perm=False)
# Initialize the main permission for the app
# After the install, if apps don't have a domain and path defined, the default url '/' is removed from the permission
permission_create(app_instance_name+".main", url="/", allowed=["all_users"])
# Execute the app install script
install_failed = True
@ -1036,12 +1095,17 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
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
# If an app doesn't have at least a domain and a path, assume it's not a webapp and remove the default "/" permission
app_settings = _get_app_settings(app_instance_name)
domain = app_settings.get('domain', None)
path = app_settings.get('path', None)
if domain and path:
permission_urls(app_instance_name+".main", add=[domain+path], sync_perm=False)
if not (domain and path):
permission_url(app_instance_name + ".main", url=None, sync_perm=False)
# Migrate classic public app still using the legacy unprotected_uris
if app_settings.get("unprotected_uris", None) == "/":
user_permission_update(app_instance_name + ".main", remove="all_users", add="visitors", sync_perm=False)
permission_sync_to_user()
logger.success(m18n.n('installation_complete'))
@ -1139,6 +1203,8 @@ def app_addaccess(apps, users=[]):
"""
from yunohost.permission import user_permission_update
logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions.")
output = {}
for app in apps:
permission = user_permission_update(app+".main", add=users, remove="all_users")
@ -1158,6 +1224,8 @@ def app_removeaccess(apps, users=[]):
"""
from yunohost.permission import user_permission_update
logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions.")
output = {}
for app in apps:
permission = user_permission_update(app+".main", remove=users)
@ -1176,6 +1244,8 @@ def app_clearaccess(apps):
"""
from yunohost.permission import user_permission_reset
logger.warning("/!\\ Packagers ! This app is using the legacy permission system. Please use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage permissions.")
output = {}
for app in apps:
permission = user_permission_reset(app+".main")
@ -1279,6 +1349,8 @@ def app_setting(app, key, value=None, delete=False):
# FIXME: Allow multiple values for some keys?
if key in ['redirected_urls', 'redirected_regex']:
value = yaml.load(value)
if key in ["unprotected_uris", "unprotected_regex", "protected_uris", "protected_regex"]:
logger.warning("/!\ Packagers ! This app is using the legacy permission system. Please delete these legacy settings and use the new helpers ynh_permission_{create,url,update,delete} and the 'visitors' group to manage public/private access.")
app_settings[key] = value
_set_app_settings(app, app_settings)
@ -1443,6 +1515,7 @@ def app_ssowatconf():
main_domain = _get_maindomain()
domains = domain_list()['domains']
all_permissions = user_permission_list(full=True)['permissions']
skipped_urls = []
skipped_regex = []
@ -1464,34 +1537,70 @@ def app_ssowatconf():
return s.split(',') if s else []
for app in apps_list:
with open(APPS_SETTING_PATH + app['id'] + '/settings.yml') as f:
app_settings = yaml.load(f)
if 'no_sso' in app_settings:
app_settings = read_yaml(APPS_SETTING_PATH + app['id'] + '/settings.yml')
if 'domain' not in app_settings:
continue
if 'path' not in app_settings:
continue
# This 'no_sso' settings sound redundant to not having $path defined ....
# At least from what I can see, all apps using it don't have a path defined ...
if 'no_sso' in app_settings:
continue
domain = app_settings['domain']
path = app_settings['path'].rstrip('/')
def _sanitized_absolute_url(perm_url):
# Nominal case : url is relative to the app's path
if perm_url.startswith("/"):
perm_domain = domain
perm_path = path + perm_url.rstrip("/")
# Otherwise, the urls starts with a domain name, like domain.tld/foo/bar
# We want perm_domain = domain.tld and perm_path = "/foo/bar"
else:
perm_domain, perm_path = perm_url.split("/", 1)
perm_path = "/" + perm_path.rstrip("/")
return perm_domain + perm_path
# Skipped
skipped_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'skipped_uris')]
skipped_regex += _get_setting(app_settings, 'skipped_regex')
# Redirected
redirected_urls.update(app_settings.get('redirected_urls', {}))
redirected_regex.update(app_settings.get('redirected_regex', {}))
# Legacy permission system using (un)protected_uris and _regex managed in app settings...
unprotected_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'unprotected_uris')]
protected_urls += [_sanitized_absolute_url(uri) for uri in _get_setting(app_settings, 'protected_uris')]
unprotected_regex += _get_setting(app_settings, 'unprotected_regex')
protected_regex += _get_setting(app_settings, 'protected_regex')
# New permission system
this_app_perms = {name: info for name, info in all_permissions.items() if name.startswith(app['id'] + ".")}
for perm_name, perm_info in this_app_perms.items():
# Ignore permissions for which there's no url defined
if not perm_info["url"]:
continue
for item in _get_setting(app_settings, 'skipped_uris'):
if item[-1:] == '/':
item = item[:-1]
skipped_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item)
for item in _get_setting(app_settings, 'skipped_regex'):
skipped_regex.append(item)
for item in _get_setting(app_settings, 'unprotected_uris'):
if item[-1:] == '/':
item = item[:-1]
unprotected_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item)
for item in _get_setting(app_settings, 'unprotected_regex'):
unprotected_regex.append(item)
for item in _get_setting(app_settings, 'protected_uris'):
if item[-1:] == '/':
item = item[:-1]
protected_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item)
for item in _get_setting(app_settings, 'protected_regex'):
protected_regex.append(item)
if 'redirected_urls' in app_settings:
redirected_urls.update(app_settings['redirected_urls'])
if 'redirected_regex' in app_settings:
redirected_regex.update(app_settings['redirected_regex'])
# FIXME : gotta handle regex-urls here... meh
url = _sanitized_absolute_url(perm_info["url"])
if "visitors" in perm_info["allowed"]:
unprotected_urls.append(url)
# Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier...
protected_urls = [u for u in protected_urls if u != url]
else:
# TODO : small optimization to implement : we don't need to explictly add all the app roots
protected_urls.append(url)
# Legacy stuff : we remove now unprotected-urls that might have been declared as protected earlier...
unprotected_urls = [u for u in unprotected_urls if u != url]
for domain in domains:
skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api'])
@ -1500,10 +1609,14 @@ def app_ssowatconf():
skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$")
skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$")
permissions_per_url = {}
for permission_name, permission_infos in user_permission_list(full=True)['permissions'].items():
for url in permission_infos["urls"]:
permissions_per_url[url] = permission_infos['corresponding_users']
for perm_name, perm_info in all_permissions.items():
# Ignore permissions for which there's no url defined
if not perm_info["url"]:
continue
permissions_per_url[perm_info["url"]] = perm_info['corresponding_users']
conf_dict = {
'portal_domain': main_domain,
@ -2683,10 +2796,8 @@ def _parse_args_in_yunohost_format(args, action_args):
if arg_value not in domain_list()['domains']:
raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('domain_unknown'))
elif arg_type == 'user':
try:
user_info(arg_value)
except YunohostError as e:
raise YunohostError('app_argument_invalid', name=arg_name, error=e)
if not arg_value in user_list()["users"].keys():
raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('user_unknown', user=arg_value))
elif arg_type == 'app':
if not _is_installed(arg_value):
raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('app_unknown'))

View file

@ -1189,7 +1189,7 @@ class RestoreManager():
return
from yunohost.user import user_group_list
from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list
from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list, permission_sync_to_user
# 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
@ -1245,14 +1245,16 @@ class RestoreManager():
# Remove all permission for all app which is still in the LDAP
for permission_name in user_permission_list(ignore_system_perms=True)["permissions"].keys():
permission_delete(permission_name, force=True)
permission_delete(permission_name, force=True, sync_perm=False)
# Restore permission for the app which is installed
for permission_name, permission_infos in old_apps_permission.items():
app_name = permission_name.split(".")[0]
if _is_installed(app_name):
permission_create(permission_name, urls=permission_infos["urls"], sync_perm=False)
user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"])
permission_create(permission_name, url=permission_infos["url"], allowed=permission_infos["allowed"], sync_perm=False)
permission_sync_to_user()
def _restore_apps(self):
"""Restore all apps targeted"""
@ -1290,7 +1292,7 @@ class RestoreManager():
restore_app_failed -- Raised if the restore bash script failed
"""
from yunohost.user import user_group_list
from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update
from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update, permission_sync_to_user
def copytree(src, dst, symlinks=False, ignore=None):
for item in os.listdir(src):
@ -1362,15 +1364,15 @@ class RestoreManager():
for permission_name, permission_infos in permissions.items():
permission_create(permission_name, urls=permission_infos.get("urls", []))
if "allowed" not in permission_infos:
logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." % (permission_name, app_instance_name))
should_be_allowed = ["all_users"]
else:
should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups]
current_allowed = user_permission_list()["permissions"][permission_name]["allowed"]
if should_be_allowed != current_allowed:
user_permission_update(permission_name, remove=current_allowed, add=should_be_allowed)
permission_create(permission_name, url=permission_infos.get("url", None), allowed=should_be_allowed, sync_perm=False)
permission_sync_to_user()
os.remove('%s/permissions.yml' % app_settings_new_path)
else:

View file

@ -9,7 +9,7 @@ from moulinette.utils.filesystem import read_yaml
from yunohost.tools import Migration
from yunohost.user import user_group_create, user_group_update
from yunohost.app import app_setting, app_list
from yunohost.regenconf import regen_conf
from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR
from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user
logger = getActionLogger('yunohost.migration')
@ -60,16 +60,21 @@ class MyMigration(Migration):
ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml')
try:
self.remove_if_exists("cn=sftpusers,ou=groups")
self.remove_if_exists("ou=permission")
self.remove_if_exists('cn=all_users,ou=groups')
self.remove_if_exists('ou=groups')
attr_dict = ldap_map['parents']['ou=permission']
ldap.add('ou=permission', attr_dict)
attr_dict = ldap_map['parents']['ou=groups']
ldap.add('ou=groups', attr_dict)
attr_dict = ldap_map['children']['cn=all_users,ou=groups']
ldap.add('cn=all_users,ou=groups', attr_dict)
attr_dict = ldap_map['children']['cn=visitors,ou=groups']
ldap.add('cn=visitors,ou=groups', attr_dict)
for rdn, attr_dict in ldap_map['depends_children'].items():
ldap.add(rdn, attr_dict)
except Exception as e:
@ -102,13 +107,21 @@ class MyMigration(Migration):
path = app_setting(app, 'path')
domain = app_setting(app, 'domain')
urls = [domain + path] if domain and path else None
permission_create(app+".main", urls=urls, sync_perm=False)
url = "/" if domain and path else None
if permission:
allowed_group = permission.split(',')
user_permission_update(app+".main", remove="all_users", add=allowed_group, sync_perm=False)
allowed_groups = permission.split(',')
else:
allowed_groups = ["all_users"]
permission_create(app+".main", url=url, allowed=allowed_groups, sync_perm=False)
app_setting(app, 'allowed_users', delete=True)
# Migrate classic public app still using the legacy unprotected_uris
if app_setting(app, "unprotected_uris") == "/":
user_permission_update(app+".main", remove="all_users", add="visitors", sync_perm=False)
permission_sync_to_user()
def run(self):
# FIXME : what do we really want to do here ...
@ -119,7 +132,7 @@ class MyMigration(Migration):
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")
logger.warning(m18n.n("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR))
# Backup LDAP and the apps settings before to do the migration
logger.info(m18n.n("migration_0011_backup_before_migration"))

View file

@ -73,7 +73,7 @@ def user_permission_list(short=False, full=False, ignore_system_perms=False):
if full:
permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])]
permissions[name]["urls"] = infos.get("URL", [])
permissions[name]["url"] = infos.get("URL", [None])[0]
if short:
permissions = permissions.keys()
@ -142,15 +142,15 @@ def user_permission_update(operation_logger, permission, add=None, remove=None,
# we shall warn the users that they should probably choose between one or the other,
# because the current situation is probably not what they expect / is temporary ?
if len(new_allowed_groups) > 1 and "all_users" in new_allowed_groups:
# FIXME : i18n
# FIXME : write a better explanation ?
logger.warning("This permission is currently enabled for all users in addition to other groups. You probably want to either remove the 'all_users' permission or remove the specific groups currently allowed.")
if len(new_allowed_groups) > 1:
if "all_users" in new_allowed_groups:
logger.warning(m18n.n("permission_currently_allowed_for_all_users"))
if "visitors" in new_allowed_groups:
logger.warning(m18n.n("permission_currently_allowed_for_visitors"))
# Don't update LDAP if we update exactly the same values
if set(new_allowed_groups) == set(current_allowed_groups):
# FIXME : i18n
logger.warning("The permission was not updated all addition/removal requests already match the current state.")
logger.warning("permission_already_up_to_date")
return
# Commit the new allowed group list
@ -212,6 +212,10 @@ def user_permission_reset(operation_logger, permission, sync_perm=True):
if existing_permission is None:
raise YunohostError('permission_not_found', permission=permission)
if existing_permission["allowed"] == ["all_users"]:
logger.warning(m18n.n("permission_already_up_to_date"))
return
# Update permission with default (all_users)
operation_logger.related_to.append(('app', permission.split(".")[0]))
@ -251,21 +255,34 @@ def user_permission_reset(operation_logger, permission, sync_perm=True):
#
# The followings methods are *not* directly exposed.
# They are used to create/delete the permissions (e.g. during app install/remove)
# and by some app helpers to possibly add additional permissions and tweak the urls
# and by some app helpers to possibly add additional permissions
#
#
@is_unit_operation()
def permission_create(operation_logger, permission, urls=None, sync_perm=True):
def permission_create(operation_logger, permission, url=None, allowed=None, sync_perm=True):
"""
Create a new permission for a specific application
Keyword argument:
permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
urls -- list of urls to specify for the permission
url -- (optional) URL for which access will be allowed/forbidden
allowed -- (optional) A list of group/user to allow for the permission
If provided, 'url' is assumed to be relative to the app domain/path if they
start with '/'. For example:
/ -> domain.tld/app
/admin -> domain.tld/app/admin
domain.tld/app/api -> domain.tld/app/api
'url' can be later treated as a regex if it starts with "re:".
For example:
re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
"""
from yunohost.user import user_group_list
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
@ -292,12 +309,22 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True):
'gidNumber': gid,
}
# If who should be allowed is explicitly provided, use this info
if allowed:
if not isinstance(allowed, list):
allowed = [allowed]
# (though first we validate that the targets actually exist)
all_existing_groups = user_group_list()['groups'].keys()
for g in allowed:
if g not in all_existing_groups:
raise YunohostError('group_unknown', group=g)
attr_dict['groupPermission'] = ['cn=%s,ou=groups,dc=yunohost,dc=org' % g for g in allowed]
# For main permission, we add all users by default
if permission.endswith(".main"):
elif permission.endswith(".main"):
attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org']
if urls:
attr_dict['URL'] = [_normalize_url(url) for url in urls]
if url:
attr_dict['URL'] = url
operation_logger.related_to.append(('app', permission.split(".")[0]))
operation_logger.start()
@ -315,15 +342,13 @@ def permission_create(operation_logger, permission, urls=None, sync_perm=True):
@is_unit_operation()
def permission_urls(operation_logger, permission, add=None, remove=None, sync_perm=True):
def permission_url(operation_logger, permission, url=None, sync_perm=True):
"""
Update urls related to a permission for a specific application
Keyword argument:
permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
add -- List of urls to add
remove -- List of urls to remove
url -- (optional) URL for which access will be allowed/forbidden
"""
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
@ -335,19 +360,9 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe
raise YunohostError('permission_not_found', permission=permission)
# Compute new url list
old_url = existing_permission["url"]
new_urls = copy.copy(existing_permission["urls"])
if add:
urls_to_add = [add] if not isinstance(add, list) else add
urls_to_add = [_normalize_url(url) for url in urls_to_add]
new_urls += urls_to_add
if remove:
urls_to_remove = [remove] if not isinstance(remove, list) else remove
urls_to_remove = [_normalize_url(url) for url in urls_to_remove]
new_urls = [u for u in new_urls if u not in urls_to_remove]
if set(new_urls) == set(existing_permission["urls"]):
if old_url == url:
logger.warning(m18n.n('permission_update_nothing_to_do'))
return existing_permission
@ -357,7 +372,7 @@ def permission_urls(operation_logger, permission, add=None, remove=None, sync_pe
operation_logger.start()
try:
ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls})
ldap.update('cn=%s,ou=permission' % permission, {'URL': [url]})
except Exception as e:
raise YunohostError('permission_update_failed', permission=permission, error=e)
@ -452,11 +467,3 @@ def permission_sync_to_user():
# Reload unscd, otherwise the group ain't propagated to the LDAP database
os.system('nscd --invalidate=passwd')
os.system('nscd --invalidate=group')
def _normalize_url(url):
from yunohost.domain import _normalize_domain_path
domain = url[:url.index('/')]
path = url[url.index('/'):]
domain, path = _normalize_domain_path(domain, path)
return domain + path

View file

@ -119,10 +119,10 @@ def app_is_exposed_on_http(domain, path, message_in_page):
return False
def install_legacy_app(domain, path):
def install_legacy_app(domain, path, public=True):
app_install("./tests/apps/legacy_app_ynh",
args="domain=%s&path=%s" % (domain, path),
args="domain=%s&path=%s&is_public=%s" % (domain, path, 1 if public else 0),
force=True)
@ -180,13 +180,7 @@ def test_legacy_app_install_secondary_domain_on_root(secondary_domain):
def test_legacy_app_install_private(secondary_domain):
install_legacy_app(secondary_domain, "/legacy")
settings = open("/etc/yunohost/apps/legacy_app/settings.yml", "r").read()
new_settings = settings.replace("\nunprotected_uris: /", "")
assert new_settings != settings
open("/etc/yunohost/apps/legacy_app/settings.yml", "w").write(new_settings)
app_ssowatconf()
install_legacy_app(secondary_domain, "/legacy", public=False)
assert app_is_installed(secondary_domain, "legacy_app")
assert not app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app")

View file

@ -529,11 +529,11 @@ def test_backup_and_restore_permission_app():
assert "permissions_app.main" in res
assert "permissions_app.admin" in res
assert "permissions_app.dev" in res
assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"]
assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"]
assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"]
assert res['permissions_app.main']['url'] == "/"
assert res['permissions_app.admin']['url'] == "/admin"
assert res['permissions_app.dev']['url'] == "/dev"
assert res['permissions_app.main']['allowed'] == ["all_users"]
assert res['permissions_app.main']['allowed'] == ["visitors"]
assert res['permissions_app.admin']['allowed'] == ["alice"]
assert res['permissions_app.dev']['allowed'] == []
@ -543,11 +543,11 @@ def test_backup_and_restore_permission_app():
assert "permissions_app.main" in res
assert "permissions_app.admin" in res
assert "permissions_app.dev" in res
assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"]
assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"]
assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"]
assert res['permissions_app.main']['url'] == "/"
assert res['permissions_app.admin']['url'] == "/admin"
assert res['permissions_app.dev']['url'] == "/dev"
assert res['permissions_app.main']['allowed'] == ["all_users"]
assert res['permissions_app.main']['allowed'] == ["visitors"]
assert res['permissions_app.admin']['allowed'] == ["alice"]
assert res['permissions_app.dev']['allowed'] == []

View file

@ -1,3 +1,4 @@
import requests
import pytest
from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map
@ -5,19 +6,20 @@ from yunohost.app import app_install, app_remove, app_change_url, app_list, app_
from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \
user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info
from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \
permission_create, permission_urls, permission_delete
permission_create, permission_delete, permission_url
from yunohost.domain import _get_maindomain
from yunohost.utils.error import YunohostError
# Get main domain
maindomain = _get_maindomain()
dummy_password = "test123Ynh"
def clean_user_groups_permission():
for u in user_list()['users']:
user_delete(u)
for g in user_group_list()['groups']:
if g != "all_users":
if g not in ["all_users", "visitors"]:
user_group_delete(g)
for p in user_permission_list()['permissions']:
@ -27,9 +29,9 @@ def clean_user_groups_permission():
def setup_function(function):
clean_user_groups_permission()
user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh")
user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh")
permission_create("wiki.main", urls=[maindomain + "/wiki"], sync_perm=False)
user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password)
user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password)
permission_create("wiki.main", url="/", sync_perm=False)
permission_create("blog.main", sync_perm=False)
user_permission_update("blog.main", remove="all_users", add="alice")
@ -39,6 +41,10 @@ def teardown_function(function):
app_remove("permissions_app")
except:
pass
try:
app_remove("legacy_app")
except:
pass
@pytest.fixture(autouse=True)
def check_LDAP_db_integrity_call():
@ -156,6 +162,31 @@ def check_permission_for_apps():
assert installed_apps == app_perms_prefix
def can_access_webpage(webpath, logged_as=None):
webpath = webpath.rstrip("/")
sso_url = "https://"+maindomain+"/yunohost/sso/"
# Anonymous access
if not logged_as:
r = requests.get(webpath, verify=False)
# Login as a user using dummy password
else:
with requests.Session() as session:
session.post(sso_url,
data={"user": logged_as,
"password": dummy_password},
headers={"Referer": sso_url,
"Content-Type": "application/x-www-form-urlencoded"},
verify=False)
# We should have some cookies related to authentication now
assert session.cookies
r = session.get(webpath, verify=False)
# If we can't access it, we got redirected to the SSO
return not r.url.startswith(sso_url)
#
# List functions
#
@ -171,7 +202,7 @@ def test_permission_list():
assert res['blog.main']['allowed'] == ["alice"]
assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"])
assert res['blog.main']['corresponding_users'] == ["alice"]
assert res['wiki.main']['urls'] == [maindomain + "/wiki"]
assert res['wiki.main']['url'] == "/"
#
# Create - Remove functions
@ -195,6 +226,15 @@ def test_permission_create_extra():
assert "all_users" not in res['site.test']['allowed']
assert res['site.test']['corresponding_users'] == []
def test_permission_create_with_allowed():
permission_create("site.test", allowed=["alice"])
res = user_permission_list(full=True)['permissions']
assert "site.test" in res
assert res['site.test']['allowed'] == ["alice"]
def test_permission_delete():
permission_delete("wiki.main", force=True)
@ -275,6 +315,17 @@ def test_permission_reset():
assert res['blog.main']['allowed'] == ["all_users"]
assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"])
def test_permission_reset_idempotency():
# Reset permission
user_permission_reset("blog.main")
user_permission_reset("blog.main")
res = user_permission_list(full=True)['permissions']
assert res['blog.main']['allowed'] == ["all_users"]
assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"])
#
# Error on update function
#
@ -291,41 +342,19 @@ def test_permission_update_permission_that_doesnt_exist():
with pytest.raises(YunohostError):
user_permission_update("doesnt.exist", add="alice")
# Permission url management
def test_permission_add_url():
permission_urls("blog.main", add=[maindomain + "/testA"])
def test_permission_redefine_url():
permission_url("blog.main", url="/pwet")
res = user_permission_list(full=True)['permissions']
assert res["blog.main"]["urls"] == [maindomain + "/testA"]
def test_permission_add_second_url():
permission_urls("wiki.main", add=[maindomain + "/testA"])
res = user_permission_list(full=True)['permissions']
assert set(res["wiki.main"]["urls"]) == set([maindomain + "/testA", maindomain + "/wiki"])
assert res["blog.main"]["url"] == "/pwet"
def test_permission_remove_url():
permission_urls("wiki.main", remove=[maindomain + "/wiki"])
permission_url("blog.main", url=None)
res = user_permission_list(full=True)['permissions']
assert res["wiki.main"]["urls"] == []
def test_permission_add_url_already_added():
res = user_permission_list(full=True)['permissions']
assert res["wiki.main"]["urls"] == [maindomain + "/wiki"]
permission_urls("wiki.main", add=[maindomain + "/wiki"])
res = user_permission_list(full=True)['permissions']
assert res["wiki.main"]["urls"] == [maindomain + "/wiki"]
def test_permission_remove_url_not_added():
permission_urls("wiki.main", remove=[maindomain + "/doesnt_exist"])
res = user_permission_list(full=True)['permissions']
assert res['wiki.main']['urls'] == [maindomain + "/wiki"]
assert res["blog.main"]["url"] is None
#
# Application interaction
@ -333,15 +362,15 @@ def test_permission_remove_url_not_added():
def test_permission_app_install():
app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
res = user_permission_list(full=True)['permissions']
assert "permissions_app.main" in res
assert "permissions_app.admin" in res
assert "permissions_app.dev" in res
assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"]
assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"]
assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"]
assert res['permissions_app.main']['url'] == "/"
assert res['permissions_app.admin']['url'] == "/admin"
assert res['permissions_app.dev']['url'] == "/dev"
assert res['permissions_app.main']['allowed'] == ["all_users"]
assert set(res['permissions_app.main']['corresponding_users']) == set(["alice", "bob"])
@ -361,7 +390,7 @@ def test_permission_app_install():
def test_permission_app_remove():
app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
app_remove("permissions_app")
# Check all permissions for this app got deleted
@ -372,14 +401,66 @@ def test_permission_app_change_url():
app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
# FIXME : should rework this test to look for differences in the generated app map / app tiles ...
res = user_permission_list(full=True)['permissions']
assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"]
assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"]
assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"]
assert res['permissions_app.main']['url'] == "/"
assert res['permissions_app.admin']['url'] == "/admin"
assert res['permissions_app.dev']['url'] == "/dev"
app_change_url("permissions_app", maindomain, "/newchangeurl")
res = user_permission_list(full=True)['permissions']
assert res['permissions_app.main']['urls'] == [maindomain + "/newchangeurl"]
assert res['permissions_app.admin']['urls'] == [maindomain + "/newchangeurl/admin"]
assert res['permissions_app.dev']['urls'] == [maindomain + "/newchangeurl/dev"]
assert res['permissions_app.main']['url'] == "/"
assert res['permissions_app.admin']['url'] == "/admin"
assert res['permissions_app.dev']['url'] == "/dev"
def test_permission_app_propagation_on_ssowat():
app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
res = user_permission_list(full=True)['permissions']
assert res['permissions_app.main']['allowed'] == ["visitors"]
app_webroot = "https://%s/urlpermissionapp" % maindomain
assert can_access_webpage(app_webroot, logged_as=None)
assert can_access_webpage(app_webroot, logged_as="alice")
user_permission_update("permissions_app.main", remove="visitors", add="bob")
res = user_permission_list(full=True)['permissions']
assert not can_access_webpage(app_webroot, logged_as=None)
assert not can_access_webpage(app_webroot, logged_as="alice")
assert can_access_webpage(app_webroot, logged_as="bob")
# Test admin access, as configured during install, only alice should be able to access it
# alice gotta be allowed on the main permission to access the admin tho
user_permission_update("permissions_app.main", remove="bob", add="all_users")
assert not can_access_webpage(app_webroot+"/admin", logged_as=None)
assert can_access_webpage(app_webroot+"/admin", logged_as="alice")
assert not can_access_webpage(app_webroot+"/admin", logged_as="bob")
def test_permission_legacy_app_propagation_on_ssowat():
app_install("./tests/apps/legacy_app_ynh",
args="domain=%s&path=%s" % (maindomain, "/legacy"), force=True)
# App is configured as public by default using the legacy unprotected_uri mechanics
# It should automatically be migrated during the install
res = user_permission_list(full=True)['permissions']
assert res['legacy_app.main']['allowed'] == ["visitors"]
app_webroot = "https://%s/legacy" % maindomain
assert can_access_webpage(app_webroot, logged_as=None)
assert can_access_webpage(app_webroot, logged_as="alice")
# Try to update the permission and check that permissions are still consistent
user_permission_update("legacy_app.main", remove="visitors", add="bob")
assert not can_access_webpage(app_webroot, logged_as=None)
assert not can_access_webpage(app_webroot, logged_as="alice")
assert can_access_webpage(app_webroot, logged_as="bob")

View file

@ -14,7 +14,7 @@ def clean_user_groups():
user_delete(u)
for g in user_group_list()['groups']:
if g != "all_users":
if g not in ["all_users", "visitors"]:
user_group_delete(g)
def setup_function(function):

View file

@ -265,7 +265,12 @@ def user_delete(operation_logger, username, purge=False):
# remove the member from the group
if username != group and username in infos["members"]:
user_group_update(group, remove=username, sync_perm=False)
user_group_delete(username, force=True, sync_perm=True)
# Delete primary group if it exists (why wouldnt it exists ? because some
# epic bug happened somewhere else and only a partial removal was
# performed...)
if username in user_group_list()['groups'].keys():
user_group_delete(username, force=True, sync_perm=True)
ldap = _get_ldap_interface()
try:
@ -632,7 +637,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
#
# We also can't delete "all_users" because that's a special group...
existing_users = user_list()['users'].keys()
undeletable_groups = existing_users + ["all_users", "admins"]
undeletable_groups = existing_users + ["all_users", "visitors"]
if groupname in undeletable_groups and not force:
raise YunohostError('group_cannot_be_deleted', group=groupname)
@ -667,13 +672,18 @@ def user_group_update(operation_logger, groupname, add=None, remove=None, force=
from yunohost.permission import permission_sync_to_user
from yunohost.utils.ldap import _get_ldap_interface
existing_users = user_list()['users'].keys()
# Refuse to edit a primary group of a user (e.g. group 'sam' related to user 'sam')
# Those kind of group should only ever contain the user (e.g. sam) and only this one.
# We also can't edit "all_users" without the force option because that's a special group...
existing_users = user_list()['users'].keys()
uneditable_groups = existing_users + ["all_users", "admins"]
if groupname in uneditable_groups and not force:
raise YunohostError('group_cannot_be_edited', group=groupname)
if not force:
if groupname == "all_users":
raise YunohostError('group_cannot_edit_all_users')
elif groupname == "visitors":
raise YunohostError('group_cannot_edit_visitors')
elif groupname in existing_users:
raise YunohostError('group_cannot_edit_primary_group', group=groupname)
# We extract the uid for each member of the group to keep a simple flat list of members
current_group = user_group_info(groupname)["members"]