Merge branch 'stretch-unstable' into patch-3

This commit is contained in:
Alexandre Aubin 2019-10-15 23:29:27 +02:00 committed by GitHub
commit 34e0d284ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1015 additions and 651 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

@ -1,31 +1,31 @@
{
"action_invalid": "Acció '{action:s}' invàlida",
"admin_password": "Contrasenya d'administració",
"admin_password_change_failed": "No s'ha pogut canviar la contrasenya",
"admin_password_change_failed": "No es pot canviar la contrasenya",
"admin_password_changed": "S'ha canviat la contrasenya d'administració",
"app_already_installed": "{app:s} ja està instal·lada",
"app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a \"app changeurl\" si està disponible.",
"app_already_up_to_date": "{app:s} ja està actualitzada",
"app_argument_choice_invalid": "Aquesta opció no és vàlida per l'argument '{name:s}', ha de ser una de {choices:s}",
"app_argument_invalid": "Valor invàlid per l'argument '{name:s}':{error:s}",
"app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices:s}» per l'argument «{name:s}»",
"app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name:s}»: {error:s}",
"app_argument_required": "Es necessita l'argument '{name:s}'",
"app_change_no_change_url_script": "L'aplicació {app_name:s} encara no permet canviar la seva URL, es possible que s'hagi d'actualitzar.",
"app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar nginx. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}",
"app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}",
"app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain:s}{path:s}'), no hi ha res per fer.",
"app_change_url_no_script": "Aquesta aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar l'aplicació.",
"app_change_url_success": "La URL de {app:s} s'ha canviat correctament a {domain:s}{path:s}",
"app_change_url_no_script": "Aquesta aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar.",
"app_change_url_success": "La URL de {app:s} ara és {domain:s}{path:s}",
"app_extraction_failed": "No s'han pogut extreure els fitxers d'instal·lació",
"app_id_invalid": "Id de l'aplicació incorrecte",
"app_id_invalid": "ID de l'aplicació incorrecte",
"app_incompatible": "L'aplicació {app} no és compatible amb la teva versió de YunoHost",
"app_install_files_invalid": "Fitxers d'instal·lació invàlids",
"app_location_already_used": "L'aplicació '{app}' ja està instal·lada en aquest camí ({path})",
"app_install_files_invalid": "Aquests fitxers no es poden instal·lar",
"app_location_already_used": "L'aplicació «{app}» ja està instal·lada en ({path})",
"app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini {domain} ja que ja és utilitzat per una altra aplicació '{other_app}'",
"app_location_install_failed": "No s'ha pogut instal·lar l'aplicació en aquest camí ja que entra en conflicte amb l'aplicació '{other_app}' ja instal·lada a '{other_path}'",
"app_location_unavailable": "Aquesta url no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}",
"app_manifest_invalid": "Manifest d'aplicació incorrecte: {error}",
"app_location_install_failed": "No s'ha pogut instal·lar l'aplicació aquí ja que entra en conflicte amb l'aplicació «{other_app}» ja instal·lada a «{other_path}»",
"app_location_unavailable": "Aquesta URL no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}",
"app_manifest_invalid": "Hi ha algun error amb el manifest de l'aplicació: {error}",
"app_no_upgrade": "No hi ha cap aplicació per actualitzar",
"app_not_correctly_installed": "{app:s} sembla estar mal instal·lada",
"app_not_installed": "L'aplicació «{app:s}» no està instal·lada. Aquí hi ha la llista d'aplicacions instal·lades: {all_apps}",
"app_not_installed": "No s'ha trobat l'aplicació «{app:s}» en la llista d'aplicacions instal·lades: {all_apps}",
"app_not_properly_removed": "{app:s} no s'ha pogut suprimir correctament",
"app_package_need_update": "El paquet de l'aplicació {app} ha de ser actualitzat per poder seguir els canvis de YunoHost",
"app_removed": "{app:s} ha estat suprimida",
@ -35,22 +35,22 @@
"app_sources_fetch_failed": "No s'han pogut carregar els fitxers font, l'URL és correcta?",
"app_unknown": "Aplicació desconeguda",
"app_unsupported_remote_type": "El tipus remot utilitzat per l'aplicació no està suportat",
"app_upgrade_app_name": "Actualitzant l'aplicació {app}…",
"app_upgrade_app_name": "Actualitzant {app}…",
"app_upgrade_failed": "No s'ha pogut actualitzar {app:s}",
"app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions",
"app_upgraded": "{app:s} ha estat actualitzada",
"app_upgraded": "S'ha actualitzat {app:s}",
"appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.",
"appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions {appslist:s}! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.",
"appslist_fetched": "S'ha descarregat la llista d'aplicacions {appslist:s} correctament",
"appslist_fetched": "S'ha actualitzat la llista d'aplicacions {appslist:s}",
"appslist_migrating": "Migrant la llista d'aplicacions {appslist:s}…",
"appslist_name_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb el nom {name:s}.",
"appslist_removed": "S'ha eliminat la llista d'aplicacions {appslist:s}",
"appslist_retrieve_bad_format": "L'arxiu obtingut per la llista d'aplicacions {appslist:s} no és vàlid",
"appslist_retrieve_bad_format": "No s'ha pogut llegir la llista d'aplicacions obtinguda {appslist:s}",
"appslist_retrieve_error": "No s'ha pogut obtenir la llista d'aplicacions remota {appslist:s}: {error:s}",
"appslist_unknown": "La llista d'aplicacions {appslist:s} es desconeguda.",
"appslist_url_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb al URL {url:s}.",
"ask_current_admin_password": "Contrasenya d'administrador actual",
"ask_email": "Correu electrònic",
"ask_email": "Adreça de correu electrònic",
"ask_firstname": "Nom",
"ask_lastname": "Cognom",
"ask_list_to_remove": "Llista per a suprimir",
@ -58,31 +58,31 @@
"ask_new_admin_password": "Nova contrasenya d'administrador",
"ask_password": "Contrasenya",
"ask_path": "Camí",
"backup_abstract_method": "Encara no s'ha implementat aquest mètode de copia de seguretat",
"backup_abstract_method": "Encara està per implementar aquest mètode de còpia de seguretat",
"backup_action_required": "S'ha d'especificar què s'ha de guardar",
"backup_app_failed": "No s'ha pogut fer la còpia de seguretat de l'aplicació \"{app:s}\"",
"backup_applying_method_borg": "Enviant tots els fitxers de la còpia de seguretat al repositori borg-backup…",
"backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat…",
"backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method:s}\"…",
"backup_applying_method_tar": "Creació de l'arxiu tar de la còpia de seguretat…",
"backup_archive_app_not_found": "L'aplicació \"{app:s}\" no es troba dins l'arxiu de la còpia de seguretat",
"backup_applying_method_tar": "Creació de l'arxiu TAR de la còpia de seguretat…",
"backup_archive_app_not_found": "No s'ha pogut trobar l'aplicació «{app:s}» dins l'arxiu de la còpia de seguretat",
"backup_archive_broken_link": "No s'ha pogut accedir a l'arxiu de la còpia de seguretat (enllaç invàlid cap a {path:s})",
"backup_archive_mount_failed": "No s'ha pogut carregar l'arxiu de la còpia de seguretat",
"backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom",
"backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom.",
"backup_archive_name_unknown": "Còpia de seguretat local \"{name:s}\" desconeguda",
"backup_archive_open_failed": "No s'ha pogut obrir l'arxiu de la còpia de seguretat",
"backup_archive_system_part_not_available": "La part \"{part:s}\" del sistema no està disponible en aquesta copia de seguretat",
"backup_archive_writing_error": "No es poden afegir arxius a l'arxiu comprimit de la còpia de seguretat",
"backup_archive_system_part_not_available": "La part «{part:s}» del sistema no està disponible en aquesta copia de seguretat",
"backup_archive_writing_error": "No es poden afegir els arxius «{source:s}» (anomenats en l'arxiu «{dest:s}») a l'arxiu comprimit de la còpia de seguretat «{archive:s}»",
"backup_ask_for_copying_if_needed": "Alguns fitxers no s'han pogut preparar per la còpia de seguretat utilitzant el mètode que evita malgastar espai del sistema temporalment. Per fer la còpia de seguretat, s'han d'utilitzar {size:s}MB temporalment. Hi esteu d'acord?",
"backup_borg_not_implemented": "El mètode de còpia de seguretat Borg encara no està implementat",
"backup_cant_mount_uncompress_archive": "No es pot carregar en mode de lectura només el directori de l'arxiu descomprimit",
"backup_cant_mount_uncompress_archive": "No es pot carregar l'arxiu descomprimit com a protegit contra escriptura",
"backup_cleaning_failed": "No s'ha pogut netejar el directori temporal de la còpia de seguretat",
"backup_copying_to_organize_the_archive": "Copiant {size:s}MB per organitzar l'arxiu",
"backup_couldnt_bind": "No es pot lligar {src:s} amb {dest:s}.",
"backup_created": "S'ha creat la còpia de seguretat",
"backup_creating_archive": "Creant l'arxiu de la còpia de seguretat…",
"aborting": "Avortant.",
"app_not_upgraded": "Les següents aplicacions no s'han actualitzat: {apps}",
"app_not_upgraded": "L'aplicació «{failed_app}» no s'ha pogut actualitzar, i com a conseqüència l'actualització de les següents aplicacions ha estat cancel·lada: {apps}",
"app_start_install": "instal·lant l'aplicació {app}…",
"app_start_remove": "Eliminant l'aplicació {app}…",
"app_start_backup": "Recuperant els fitxers pels que s'ha de fer una còpia de seguretat per {app}…",
@ -90,22 +90,22 @@
"app_upgrade_several_apps": "S'actualitzaran les següents aplicacions: {apps}",
"ask_new_domain": "Nou domini",
"ask_new_path": "Nou camí",
"backup_actually_backuping": "S'està creant un arxiu de còpia de seguretat a partir dels fitxers recuperats…",
"backup_creation_failed": "Ha fallat la creació de la còpia de seguretat",
"backup_actually_backuping": "Creant un arxiu de còpia de seguretat a partir dels fitxers recuperats…",
"backup_creation_failed": "No s'ha pogut crear l'arxiu de la còpia de seguretat",
"backup_csv_addition_failed": "No s'han pogut afegir fitxers per a fer-ne la còpia de seguretat al fitxer CSV",
"backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a futures operacions de recuperació",
"backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"backup\"",
"backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"mount\"",
"backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a la restauració",
"backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «backup»",
"backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «mount»",
"backup_custom_need_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"need_mount\"",
"backup_delete_error": "No s'ha pogut suprimir \"{path:s}\"",
"backup_delete_error": "No s'ha pogut suprimir «{path:s}»",
"backup_deleted": "S'ha suprimit la còpia de seguretat",
"backup_extracting_archive": "Extraient l'arxiu de la còpia de seguretat…",
"backup_hook_unknown": "Script de còpia de seguretat \"{hook:s}\" desconegut",
"backup_invalid_archive": "Arxiu de còpia de seguretat no vàlid",
"backup_method_borg_finished": "La còpia de seguretat a borg ha acabat",
"backup_hook_unknown": "Script de còpia de seguretat «{hook:s}» desconegut",
"backup_invalid_archive": "Aquest no és un arxiu de còpia de seguretat",
"backup_method_borg_finished": "La còpia de seguretat a Borg ha acabat",
"backup_method_copy_finished": "La còpia de la còpia de seguretat ha acabat",
"backup_method_custom_finished": "El mètode de còpia de seguretat personalitzat \"{method:s}\" ha acabat",
"backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat tar",
"backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat TAR",
"backup_mount_archive_for_restore": "Preparant l'arxiu per la restauració…",
"good_practices_about_user_password": "Esteu a punt de definir una nova contrasenya d'usuari. La contrasenya ha de tenir un mínim de 8 caràcters; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).",
"password_listed": "Aquesta contrasenya és una de les més utilitzades en el món. Si us plau utilitzeu-ne una més única.",
@ -115,42 +115,42 @@
"password_too_simple_4": "La contrasenya ha de tenir un mínim de 12 caràcters i tenir dígits, majúscules, minúscules i caràcters especials",
"backup_no_uncompress_archive_dir": "El directori de l'arxiu descomprimit no existeix",
"backup_nothings_done": "No hi ha res a guardar",
"backup_output_directory_forbidden": "Directori de sortida no permès. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives",
"backup_output_directory_not_empty": "El directori de sortida no està buit",
"backup_output_directory_forbidden": "Escolliu un directori de sortida different. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives",
"backup_output_directory_not_empty": "Heu d'escollir un directori de sortida buit",
"backup_output_directory_required": "Heu d'especificar un directori de sortida per la còpia de seguretat",
"backup_output_symlink_dir_broken": "Teniu un enllaç simbòlic trencat en lloc del directori dels arxius '{path:s}'. Pot ser teniu una configuració per la còpia de seguretat específica en un altre sistema de fitxers, si és el cas segurament heu oblidat muntar o connectar el disc dur o la clau USB.",
"backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar php7, la restauració de les vostres aplicacions pot fallar (raó: {error:s})",
"backup_output_symlink_dir_broken": "Teniu un enllaç simbòlic trencat en lloc del directori del arxiu «{path:s}». Pot ser teniu una configuració per la còpia de seguretat específica en un altre sistema de fitxers, si és el cas segurament heu oblidat muntar o connectar el disc dur o la clau USB.",
"backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar PHP 7, pot ser que no es puguin restaurar les vostres aplicacions PHP (raó: {error:s})",
"backup_running_hooks": "Executant els scripts de la còpia de seguretat…",
"backup_system_part_failed": "No s'ha pogut fer la còpia de seguretat de la part \"{part:s}\" del sistema",
"backup_unable_to_organize_files": "No s'han pogut organitzar els fitxers dins de l'arxiu amb el mètode ràpid",
"backup_with_no_backup_script_for_app": "L'aplicació {app:s} no té un script de còpia de seguretat. Serà ignorat.",
"backup_with_no_restore_script_for_app": "L'aplicació {app:s} no té un script de restauració, no podreu restaurar automàticament la còpia de seguretat d'aquesta aplicació.",
"certmanager_acme_not_configured_for_domain": "El certificat pel domini {domain:s} sembla que no està instal·lat correctament. Si us plau executeu primer cert-install per aquest domini.",
"certmanager_attempt_to_renew_nonLE_cert": "El certificat pel domini {domain:s} no ha estat emès per Let's Encrypt. No es pot renovar automàticament!",
"certmanager_attempt_to_renew_valid_cert": "El certificat pel domini {domain:s} està a punt de caducar! (Utilitzeu --force si sabeu el que esteu fent)",
"backup_unable_to_organize_files": "No s'ha pogut utilitzar el mètode ràpid per organitzar els fitxers dins de l'arxiu",
"backup_with_no_backup_script_for_app": "L'aplicació «{app:s}» no té un script de còpia de seguretat. Serà ignorat.",
"backup_with_no_restore_script_for_app": "L'aplicació «{app:s}» no té un script de restauració, no podreu restaurar automàticament la còpia de seguretat d'aquesta aplicació.",
"certmanager_acme_not_configured_for_domain": "El certificat pel domini «{domain:s}» sembla que no està instal·lat correctament. Si us plau executeu primer «cert-install» per aquest domini.",
"certmanager_attempt_to_renew_nonLE_cert": "El certificat pel domini «{domain:s}» no ha estat emès per Let's Encrypt. No es pot renovar automàticament!",
"certmanager_attempt_to_renew_valid_cert": "El certificat pel domini «{domain:s}» està a punt de caducar! (Utilitzeu --force si sabeu el que esteu fent)",
"certmanager_attempt_to_replace_valid_cert": "Esteu intentant sobreescriure un certificat correcte i vàlid pel domini {domain:s}! (Utilitzeu --force per ometre)",
"certmanager_cannot_read_cert": "S'ha produït un error al intentar obrir el certificat actual pel domini {domain:s} (arxiu: {file:s}), raó: {reason:s}",
"certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini {domain:s}!",
"certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini {domain:s}!",
"certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini {domain:s}!",
"certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini «{domain:s}»",
"certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini «{domain:s}»",
"certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini «{domain:s}»",
"certmanager_cert_signing_failed": "No s'ha pogut firmar el nou certificat",
"certmanager_certificate_fetching_or_enabling_failed": "Sembla que l'activació del nou certificat per {domain:s} ha fallat…",
"certmanager_conflicting_nginx_file": "No s'ha pogut preparar el domini per al desafiament ACME: l'arxiu de configuració nginx {filepath:s} entra en conflicte i s'ha d'eliminar primer",
"certmanager_certificate_fetching_or_enabling_failed": "Sembla que utilitzar el nou certificat per {domain:s} ha fallat…",
"certmanager_conflicting_nginx_file": "No s'ha pogut preparar el domini per al desafiament ACME: l'arxiu de configuració NGINX {filepath:s} entra en conflicte i s'ha d'eliminar primer",
"certmanager_couldnt_fetch_intermediate_cert": "S'ha exhaurit el temps d'esperar al intentar recollir el certificat intermedi des de Let's Encrypt. La instal·lació/renovació del certificat s'ha cancel·lat - torneu a intentar-ho més tard.",
"certmanager_domain_cert_not_selfsigned": "El certificat pel domini {domain:s} no és auto-signat Esteu segur de voler canviar-lo? (Utilitzeu --force per fer-ho)",
"certmanager_domain_dns_ip_differs_from_public_ip": "El registre DNS \"A\" pel domini {domain:s} és diferent a l'adreça IP d'aquest servidor. Si heu modificat recentment el registre A, si us plau espereu a que es propagui (hi ha eines per verificar la propagació disponibles a internet). (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)",
"certmanager_domain_http_not_working": "Sembla que el domini {domain:s} no és accessible via HTTP. Si us plau verifiqueu que les configuracions DNS i nginx siguin correctes",
"certmanager_domain_cert_not_selfsigned": "El certificat pel domini {domain:s} no és auto-signat Esteu segur de voler canviar-lo? (Utilitzeu «--force» per fer-ho)",
"certmanager_domain_dns_ip_differs_from_public_ip": "El registre DNS \"A\" pel domini «{domain:s}» és diferent a l'adreça IP d'aquest servidor. Si heu modificat recentment el registre A, si us plau espereu a que es propagui (hi ha eines per verificar la propagació disponibles a internet). (Si sabeu el que esteu fent, podeu utilitzar «--no-checks» per desactivar aquestes comprovacions.)",
"certmanager_domain_http_not_working": "Sembla que el domini {domain:s} no és accessible via HTTP. Verifiqueu que les configuracions DNS i NGINX siguin correctes",
"certmanager_domain_not_resolved_locally": "El domini {domain:s} no es pot resoldre dins del vostre servidor YunoHost. Això pot passar si heu modificat recentment el registre DNS. Si és així, si us plau espereu unes hores per a que es propagui. Si el problema continua, considereu afegir {domain:s} a /etc/hosts. (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)",
"certmanager_domain_unknown": "Domini desconegut {domain:s}",
"certmanager_error_no_A_record": "No s'ha trobat cap registre DNS \"A\" per {domain:s}. Heu de fer que el vostre nom de domini apunti cap a la vostra màquina per tal de poder instal·lar un certificat Let's Encrypt! (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)",
"certmanager_domain_unknown": "Domini desconegut «{domain:s}»",
"certmanager_error_no_A_record": "No s'ha trobat cap registre DNS «A» per «{domain:s}». Heu de fer que el vostre nom de domini apunti cap a la vostra màquina per tal de poder instal·lar un certificat Let's Encrypt. (Si sabeu el que esteu fent, podeu utilitzar «--no-checks» per desactivar aquestes comprovacions.)",
"certmanager_hit_rate_limit": "S'han emès massa certificats recentment per aquest mateix conjunt de dominis {domain:s}. Si us plau torneu-ho a intentar més tard. Consulteu https://letsencrypt.org/docs/rate-limits/ per obtenir més detalls",
"certmanager_http_check_timeout": "S'ha exhaurit el temps d'espera quan el servidor ha intentat contactar amb ell mateix via HTTP utilitzant la seva adreça IP pública (domini domain:s} amb IP {ip:s}). Pot ser degut a hairpinning o a que el talla focs/router al que està connectat el servidor estan mal configurats.",
"certmanager_http_check_timeout": "S'ha exhaurit el temps d'espera quan el servidor ha intentat contactar amb ell mateix via HTTP utilitzant la seva adreça IP pública (domini «{domain:s}» amb IP «{ip:s}»). Pot ser degut a hairpinning o a que el talla focs/router al que està connectat el servidor estan mal configurats.",
"certmanager_no_cert_file": "No s'ha pogut llegir l'arxiu del certificat pel domini {domain:s} (fitxer: {file:s})",
"certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file:s})",
"certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file:s})",
"confirm_app_install_warning": "Atenció: aquesta aplicació funciona però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ",
"confirm_app_install_danger": "ATENCIÓ! Aquesta aplicació encara és experimental (si no és que no funciona directament) i és probable que trenqui el sistema! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc? [{answers:s}] ",
"confirm_app_install_thirdparty": "ATENCIÓ! La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. Faci-ho sota la seva responsabilitat.No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc? [{answers:s}] ",
"confirm_app_install_warning": "Atenció: Aquesta aplicació funciona, però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ",
"confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema... Si accepteu el risc, escriviu «{answers:s}»",
"confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»",
"custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app:s}",
"custom_appslist_name_required": "Heu d'especificar un nom per la vostra llista d'aplicacions personalitzada",
"diagnosis_debian_version_error": "No s'ha pogut obtenir la versió Debian: {error}",
@ -160,14 +160,14 @@
"diagnosis_monitor_system_error": "No es pot monitorar el sistema: {error}",
"diagnosis_no_apps": "No hi ha cap aplicació instal·lada",
"admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters",
"dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/apt (els gestors de paquets del sistema) sembla estar mal configurat... Podeu intentar solucionar-ho connectant-vos per ssh i executant \"sudo dpkg --configure -a\".",
"dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».",
"dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"",
"domain_cannot_remove_main": "No es pot eliminar el domini principal. S'ha d'establir un nou domini primer",
"domain_cert_gen_failed": "No s'ha pogut generar el certificat",
"domain_created": "S'ha creat el domini",
"domain_creation_failed": "No s'ha pogut crear el domini",
"domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}",
"domain_deleted": "S'ha eliminat el domini",
"domain_deletion_failed": "No s'ha pogut eliminar el domini",
"domain_deletion_failed": "No s'ha pogut eliminar el domini {domini}: {error}",
"domain_exists": "El domini ja existeix",
"app_action_cannot_be_ran_because_required_services_down": "Aquesta aplicació necessita serveis que estan aturats. Abans de continuar, hauríeu d'intentar arrancar de nou els serveis següents (i també investigar perquè estan aturats): {services}",
"domain_dns_conf_is_just_a_recommendation": "Aquesta ordre mostra la configuració *recomanada*. En cap cas fa la configuració del DNS. És la vostra responsabilitat configurar la zona DNS en el vostre registrar en acord amb aquesta recomanació.",
@ -175,7 +175,7 @@
"domain_dyndns_dynette_is_unreachable": "No s'ha pogut abastar la dynette YunoHost, o bé YunoHost no està connectat a internet correctament o bé el servidor dynette està caigut. Error: {error}",
"domain_dyndns_invalid": "Domini no vàlid per utilitzar amb DynDNS",
"domain_dyndns_root_unknown": "Domini DynDNS principal desconegut",
"domain_hostname_failed": "No s'ha pogut establir un nou nom d'amfitrió. Això podria causar problemes més tard (no és segur ... podria no passar res).",
"domain_hostname_failed": "No s'ha pogut establir un nou nom d'amfitrió. Això podria causar problemes més tard (podria no passar res).",
"domain_uninstall_app_first": "Hi ha una o més aplicacions instal·lades en aquest domini. Desinstal·leu les abans d'eliminar el domini",
"domain_unknown": "Domini desconegut",
"domain_zone_exists": "El fitxer de zona DNS ja existeix",
@ -187,61 +187,61 @@
"dyndns_could_not_check_available": "No s'ha pogut verificar la disponibilitat de {domain:s} a {provider:s}.",
"dyndns_ip_update_failed": "No s'ha pogut actualitzar l'adreça IP al DynDNS",
"dyndns_ip_updated": "S'ha actualitzat l'adreça IP al DynDNS",
"dyndns_key_generating": "S'està generant la clau DNS, això pot trigar una estona…",
"dyndns_key_generating": "S'està generant la clau DNS… això pot trigar una estona.",
"dyndns_key_not_found": "No s'ha trobat la clau DNS pel domini",
"dyndns_no_domain_registered": "No hi ha cap domini registrat amb DynDNS",
"dyndns_registered": "S'ha registrat el domini DynDNS",
"dyndns_registration_failed": "No s'ha pogut registrar el domini DynDNS: {error:s}",
"dyndns_domain_not_provided": "El proveïdor {provider:s} no pot oferir el domini {domain:s}.",
"dyndns_domain_not_provided": "El proveïdor de DynDNS {provider:s} no pot oferir el domini {domain:s}.",
"dyndns_unavailable": "El domini {domain:s} no està disponible.",
"executing_command": "Execució de l'ordre « {command:s} »…",
"executing_script": "Execució de l'script « {script:s} »…",
"extracting": "Extracció en curs…",
"dyndns_cron_installed": "S'ha instal·lat la tasca cron pel DynDNS",
"dyndns_cron_installed": "S'ha creat la tasca cron pel DynDNS",
"dyndns_cron_remove_failed": "No s'ha pogut eliminar la tasca cron per a DynDNS: {error}",
"dyndns_cron_removed": "S'ha eliminat la tasca cron pel DynDNS",
"experimental_feature": "Atenció: aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.",
"experimental_feature": "Atenció: Aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.",
"field_invalid": "Camp incorrecte « {:s} »",
"file_does_not_exist": "El camí {path:s} no existeix.",
"firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafoc",
"firewall_reloaded": "S'ha tornat a carregar el tallafoc",
"firewall_rules_cmd_failed": "No s'han pogut aplicar algunes regles del tallafoc. Mireu el registre per a més informació.",
"firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafocs",
"firewall_reloaded": "S'ha tornat a carregar el tallafocs",
"firewall_rules_cmd_failed": "No s'han pogut aplicar algunes regles del tallafocs. Més informació en el registre.",
"format_datetime_short": "%d/%m/%Y %H:%M",
"global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}» però les opcions disponibles són: {available_choices:s}",
"global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}», però les opcions disponibles són: {available_choices:s}",
"global_settings_bad_type_for_setting": "El tipus del paràmetre {setting:s} és incorrecte. S'ha rebut {received_type:s}, però s'esperava {expected_type:s}",
"global_settings_cant_open_settings": "No s'ha pogut obrir el fitxer de configuració, raó: {reason:s}",
"global_settings_cant_serialize_settings": "No s'ha pogut serialitzar les dades de configuració, raó: {reason:s}",
"global_settings_cant_write_settings": "No s'ha pogut escriure el fitxer de configuració, raó: {reason:s}",
"global_settings_key_doesnt_exists": "La clau « {settings_key:s} » no existeix en la configuració global, podeu veure totes les claus disponibles executant «yunohost settings list »",
"global_settings_reset_success": "Èxit. S'ha fet una còpia de seguretat de la configuració anterior a {path:s}",
"global_settings_reset_success": "S'ha fet una còpia de seguretat de la configuració anterior a {path:s}",
"global_settings_setting_example_bool": "Exemple d'opció booleana",
"global_settings_setting_example_enum": "Exemple d'opció de tipus enumeració",
"global_settings_setting_example_int": "Exemple d'opció de tipus enter",
"global_settings_setting_example_string": "Exemple d'opció de tipus cadena",
"global_settings_setting_security_nginx_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor web nginx. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"global_settings_setting_security_nginx_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor web NGINX. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"global_settings_setting_security_password_admin_strength": "Robustesa de la contrasenya d'administrador",
"global_settings_setting_security_password_user_strength": "Robustesa de la contrasenya de l'usuari",
"global_settings_setting_security_ssh_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor SSH. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key:s}», refusant-la i guardant-la a /etc/yunohost/settings-unknown.json",
"global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key:s}», refusada i guardada a /etc/yunohost/settings-unknown.json",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permetre la clau d'hoste DSA (obsolet) per la configuració del servei SSH",
"global_settings_unknown_type": "Situació inesperada, la configuració {setting:s} sembla tenir el tipus {unknown_type:s} però no és un tipus reconegut pel sistema.",
"good_practices_about_admin_password": "Esteu a punt de definir una nova contrasenya d'administrador. La contrasenya ha de tenir un mínim de 8 caràcters; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).",
"hook_exec_failed": "No s'ha pogut executar l'script: {path:s}",
"hook_exec_not_terminated": "L'execució de l'script « {path:s} » no s'ha acabat correctament",
"hook_json_return_error": "No s'ha pogut llegir el retorn de l'script {path:s}. Error: {msg:s}. Contingut en brut: {raw_content}",
"hook_list_by_invalid": "Propietat per llistar les accions invàlida",
"hook_exec_failed": "No s'ha pogut executar el script: {path:s}",
"hook_exec_not_terminated": "El script no s'ha acabat correctament: {path:s}",
"hook_json_return_error": "No s'ha pogut llegir el retorn del script {path:s}. Error: {msg:s}. Contingut en brut: {raw_content}",
"hook_list_by_invalid": "Aquesta propietat no es pot utilitzar per llistar els hooks",
"hook_name_unknown": "Nom de script « {name:s} »desconegut",
"installation_complete": "Instal·lació completada",
"installation_failed": "Ha fallat la instal·lació",
"installation_failed": "Ha fallat alguna cosa amb la instal·lació",
"invalid_url_format": "Format d'URL invàlid",
"ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció",
"iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció",
"log_corrupted_md_file": "El fitxer de metadades yaml associat amb els registres està malmès: « {md_file} »\nError: {error}",
"log_corrupted_md_file": "El fitxer de metadades YAML associat amb els registres està malmès: « {md_file} »\nError: {error}",
"log_category_404": "La categoria de registres « {category} » no existeix",
"log_link_to_log": "El registre complet d'aquesta operació: «<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>»",
"log_help_to_get_log": "Per veure el registre de l'operació « {desc} », utilitzeu l'ordre «yunohost log display {name} »",
"log_link_to_failed_log": "L'operació « {dec} » ha fallat! Per obtenir ajuda, <a href=\"#/tools/logs/{name}\">proveïu el registre complete de l'operació clicant aquí</a>",
"log_help_to_get_failed_log": "L'operació « {dec} » ha fallat! Per obtenir ajuda, compartiu el registre complete de l'operació utilitzant l'ordre «yunohost log display {name} --share»",
"log_link_to_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, <a href=\"#/tools/logs/{name}\">proveïu el registre complete de l'operació clicant aquí</a>",
"log_help_to_get_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, compartiu el registre complete de l'operació utilitzant l'ordre «yunohost log display {name} --share»",
"log_does_exists": "No hi ha cap registre per l'operació amb el nom«{log} », utilitzeu «yunohost log list» per veure tots els registre d'operació disponibles",
"log_operation_unit_unclosed_properly": "L'operació no s'ha tancat de forma correcta",
"log_app_addaccess": "Afegir accés a « {} »",
@ -263,7 +263,7 @@
"log_domain_remove": "Elimina el domini « {} » de la configuració del sistema",
"log_dyndns_subscribe": "Subscriure's a un subdomini YunoHost « {} »",
"log_dyndns_update": "Actualitza la IP associada al subdomini YunoHost « {} »",
"log_letsencrypt_cert_install": "Instal·la el certificat Let's Encrypt al domini « {} »",
"log_letsencrypt_cert_install": "Instal·la un certificat Let's Encrypt al domini « {} »",
"log_selfsigned_cert_install": "Instal·la el certificat autosignat al domini « {} »",
"log_letsencrypt_cert_renew": "Renova el certificat Let's Encrypt de « {} »",
"log_service_enable": "Activa el servei « {} »",
@ -278,79 +278,79 @@
"log_tools_upgrade": "Actualitza els paquets del sistema",
"log_tools_shutdown": "Apaga el servidor",
"log_tools_reboot": "Reinicia el servidor",
"already_up_to_date": "No hi ha res a fer! Tot està al dia!",
"already_up_to_date": "No hi ha res a fer. Tot està actualitzat.",
"dpkg_lock_not_available": "No es pot utilitzar aquesta comanda en aquest moment ja que sembla que un altre programa està utilitzant el lock de dpkg (el gestor de paquets del sistema)",
"global_settings_setting_security_postfix_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor Postfix. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"ldap_init_failed_to_create_admin": "La inicialització de LDAP no ha pogut crear l'usuari admin",
"ldap_initialized": "S'ha iniciat LDAP",
"license_undefined": "indefinit",
"mail_alias_remove_failed": "No s'han pogut eliminar els alias del correu «{mail:s}»",
"mail_domain_unknown": "Domini d'adreça de correu «{domain:s}» desconegut",
"mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail:s}»",
"mail_domain_unknown": "Domini d'adreça de correu per «{domain:s}» desconegut",
"mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail:s}»",
"mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot per poder obtenir l'espai utilitzat per la bústia de correu",
"mail_unavailable": "Aquesta adreça de correu esta reservada i ha de ser atribuïda automàticament el primer usuari",
"mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot, per poder obtenir l'espai utilitzat per la bústia de correu",
"mail_unavailable": "Aquesta adreça de correu està reservada i ha de ser atribuïda automàticament el primer usuari",
"maindomain_change_failed": "No s'ha pogut canviar el domini principal",
"maindomain_changed": "S'ha canviat el domini principal",
"migrate_tsig_end": "La migració cap a hmac-sha512 s'ha acabat",
"migrate_tsig_failed": "Ha fallat la migració del domini dyndns {domain} cap a hmac-sha512, anul·lant les modificacions. Error: {error_code} - {error}",
"migrate_tsig_start": "L'algoritme de generació de claus no es prou segur per a la signatura TSIG del domini «{domain}», començant la migració cap a un de més segur hmac-sha512",
"migrate_tsig_wait": "Esperar 3 minuts per a que el servidor dyndns tingui en compte la nova clau…",
"migrate_tsig_end": "La migració cap a HMAC-SHA-512 s'ha acabat",
"migrate_tsig_failed": "Ha fallat la migració del domini DynDNS «{domain}» cap a HMAC-SHA-512, anul·lant les modificacions. Error: {error_code}, {error}",
"migrate_tsig_start": "L'algoritme de generació de claus no es prou segur per a la signatura TSIG del domini «{domain}», començant la migració cap a un de més segur HMAC-SHA-512",
"migrate_tsig_wait": "Esperant tres minuts per a que el servidor DynDNS tingui en compte la nova clau…",
"migrate_tsig_wait_2": "2 minuts…",
"migrate_tsig_wait_3": "1 minut…",
"migrate_tsig_wait_4": "30 segons…",
"migrate_tsig_not_needed": "Sembla que no s'utilitza cap domini dyndns, no és necessari fer cap migració!",
"migrate_tsig_not_needed": "Sembla que no s'utilitza cap domini DynDNS, no és necessari fer cap migració.",
"migration_description_0001_change_cert_group_to_sslcert": "Canvia els permisos del grup dels certificats de «metronome»a «ssl-cert»",
"migration_description_0002_migrate_to_tsig_sha256": "Millora la seguretat de dyndns TSIG utilitzant SHA512 en lloc de MD5",
"migration_description_0002_migrate_to_tsig_sha256": "Millora la seguretat de DynDNS TSIG utilitzant SHA-512 en lloc de MD5",
"migration_description_0003_migrate_to_stretch": "Actualització del sistema a Debian Stretch i YunoHost 3.0",
"migration_description_0004_php5_to_php7_pools": "Tornar a configurar els pools PHP per utilitzar PHP 7 en lloc de PHP 5",
"migration_description_0005_postgresql_9p4_to_9p6": "Migració de les bases de dades de postgresql 9.4 a 9.6",
"migration_description_0005_postgresql_9p4_to_9p6": "Migració de les bases de dades de PostgreSQL 9.4 a 9.6",
"migration_description_0006_sync_admin_and_root_passwords": "Sincronitzar les contrasenyes admin i root",
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuració SSH serà gestionada per YunoHost (pas 1, automàtic)",
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuració SSH serà gestionada per YunoHost (pas 2, manual)",
"migration_description_0009_decouple_regenconf_from_services": "Desvincula el mecanisme regen-conf dels serveis",
"migration_description_0010_migrate_to_apps_json": "Elimina la appslists (desfasat) i utilitza la nova llista unificada «apps.json» en el seu lloc",
"migration_description_0010_migrate_to_apps_json": "Elimina les llistes d'aplicacions obsoletes i utilitza la nova llista unificada «apps.json» en el seu lloc",
"migration_0003_backward_impossible": "La migració Stretch no és reversible.",
"migration_0003_start": "Ha començat la migració a Stretch. Els registres estaran disponibles a {logfile}.",
"migration_0003_patching_sources_list": "Modificant el fitxer sources.lists…",
"migration_0003_main_upgrade": "Començant l'actualització principal…",
"migration_0003_fail2ban_upgrade": "Començant l'actualització de fail2ban…",
"migration_0003_fail2ban_upgrade": "Començant l'actualització de Fail2Ban…",
"migration_0003_restoring_origin_nginx_conf": "El fitxer /etc/nginx/nginx.conf ha estat editat. La migració el tornarà al seu estat original... El fitxer anterior estarà disponible com a {backup_dest}.",
"migration_0003_yunohost_upgrade": "Començant l'actualització del paquet yunohost... La migració acabarà, però l'actualització actual es farà just després. Després de completar aquesta operació, pot ser que us hagueu de tornar a connectar a la web d'administració.",
"migration_0003_yunohost_upgrade": "Començant l'actualització del paquet YunoHost... La migració acabarà, però l'actualització actual es farà just després. Després de completar aquesta operació, pot ser que us hagueu de tornar a connectar a la web d'administració.",
"migration_0003_not_jessie": "La distribució Debian actual no és Jessie!",
"migration_0003_system_not_fully_up_to_date": "El vostre sistema no està completament actualitzat. S'ha de fer una actualització normal abans de fer la migració a Stretch.",
"migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: el sistema encara està amb Jessie!? Per investigar el problema, mireu el registres a {log}:s…",
"migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. Tot i que l'equip de YunoHost a fet els possibles per revisar-la i provar-la, la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, recomanem:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port 465 serà tancat automàticament i el nou port 587 serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis!",
"migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'una applist o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}",
"migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: El sistema encara està amb Jessie? Per investigar el problema, mireu el registres a {log}:s…",
"migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. L'equip de YunoHost a fet els possibles per revisar-la i provar-la, però la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, es recomana:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port (465) serà tancat automàticament, i el nou port (587) serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis.",
"migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'una applist, o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}",
"migration_0003_modified_files": "Tingueu en compte que s'han detectat els següents fitxers que han estat modificats manualment i podrien sobreescriure's al final de l'actualització: {manually_modified_files}",
"migration_0005_postgresql_94_not_installed": "Postgresql no està instal·lat en el sistema. No hi ha res per fer!",
"migration_0005_postgresql_96_not_installed": "S'ha trobat Postgresql 9.4 instal·lat, però no Postgresql 9.6!? Alguna cosa estranya a passat en el sistema :( …",
"migration_0005_not_enough_space": "No hi ha prou espai disponible en {path} per fer la migració en aquest moment :(.",
"migration_0005_postgresql_94_not_installed": "PostgreSQL no està instal·lat en el sistema. No hi ha res per fer.",
"migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 està instal·lat, però no PostgreSQL 9.6? Alguna cosa estranya a passat en el sistema :( …",
"migration_0005_not_enough_space": "Creu espai disponible en {path} per executar la migració.",
"migration_0006_disclaimer": "YunoHost esperar que les contrasenyes admin i root estiguin sincronitzades. Fent aquesta migració, la contrasenya root serà reemplaçada per la contrasenya admin.",
"migration_0007_cancelled": "YunoHost no ha pogut millorar la gestió de la configuració SSH.",
"migration_0007_cannot_restart": "No es pot reiniciar SSH després d'haver intentat cancel·lar la migració numero 6.",
"migration_0008_general_disclaimer": "Per millorar la seguretat del servidor, es recomana que sigui YunoHost qui gestioni la configuració SSH. La configuració SSH actual és diferent a la configuració recomanada. Si deixeu que YunoHost ho reconfiguri, la manera de connectar-se al servidor mitjançant SSH canviarà de la següent manera:",
"migration_0008_port": " - la connexió es farà utilitzant el port 22 en lloc del port SSH personalitzat actual. Es pot reconfigurar;",
"migration_0008_root": " - no es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;",
"migration_0008_dsa": " - es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;",
"migration_0008_port": "• La connexió es farà utilitzant el port 22 en lloc del port SSH personalitzat actual. Es pot reconfigurar;",
"migration_0008_root": "• No es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;",
"migration_0008_dsa": "• Es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;",
"migration_0008_warning": "Si heu entès els avisos i accepteu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.",
"migration_0008_no_warning": "No s'han detectat riscs importants per sobreescriure la configuració SSH, però no es pot estar del tot segur ;)! Si accepteu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.",
"migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració? Ometent.",
"migration_0008_no_warning": "No s'han identificat riscs importants per sobreescriure la configuració SSH, però no es pot estar del tot segur ;)! Executetu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.",
"migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració… (?) Ometent.",
"migrations_backward": "Migració cap enrere.",
"migrations_bad_value_for_target": "Nombre invàlid pel paràmetre target, els nombres de migració disponibles són 0 o {}",
"migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí %s",
"migrations_current_target": "La migració objectiu és {}",
"migrations_error_failed_to_load_migration": "ERROR: no s'ha pogut carregar la migració {number} {name}",
"migrations_forward": "Migració endavant",
"migrations_list_conflict_pending_done": "No es pot utilitzar --previous i --done al mateix temps.",
"migrations_loading_migration": "Carregant la migració {number} {name}…",
"migrations_migration_has_failed": "La migració {number} {name} ha fallat amb l'excepció {exception}, cancel·lant",
"migrations_list_conflict_pending_done": "No es pot utilitzar «--previous» i «--done» al mateix temps.",
"migrations_loading_migration": "Carregant la migració {id}…",
"migrations_migration_has_failed": "La migració {id} ha fallat, cancel·lant. Error: {exception}",
"migrations_no_migrations_to_run": "No hi ha cap migració a fer",
"migrations_show_currently_running_migration": "Fent la migració {number} {name}…",
"migrations_show_last_migration": "L'última migració feta és {}",
"migrations_skip_migration": "Saltant migració {number} {name}…",
"migrations_skip_migration": "Saltant migració {id}…",
"migrations_success": "S'ha completat la migració {number} {name} amb èxit!",
"migrations_to_be_ran_manually": "La migració {number} {name} s'ha de fer manualment. Aneu a Eines > Migracions a la interfície admin, o executeu «yunohost tools migrations migrate».",
"migrations_need_to_accept_disclaimer": "Per fer la migració {number} {name}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció --accept-disclaimer.",
"migrations_to_be_ran_manually": "La migració {id} s'ha de fer manualment. Aneu a Eines → Migracions a la interfície admin, o executeu «yunohost tools migrations migrate».",
"migrations_need_to_accept_disclaimer": "Per fer la migració {id}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció «--accept-disclaimer».",
"monitor_disabled": "El monitoratge del servidor ha estat desactivat",
"monitor_enabled": "El monitoratge del servidor ha estat activat",
"monitor_glances_con_failed": "No s'ha pogut connectar al servidor Glances",
@ -384,18 +384,18 @@
"pattern_firstname": "Ha de ser un nom vàlid",
"pattern_lastname": "Ha de ser un cognom vàlid",
"pattern_listname": "Ha d'estar compost per caràcters alfanumèrics i guió baix exclusivament",
"pattern_mailbox_quota": "Ha de ser una mida amb el sufix b/k/M/G/T o 0 per desactivar la quota",
"pattern_mailbox_quota": "Ha de ser una mida amb el sufix b/k/M/G/T o 0 per no tenir quota",
"pattern_password": "Ha de tenir un mínim de 3 caràcters",
"pattern_port": "Ha de ser un número de port vàlid (i.e. 0-65535)",
"pattern_port_or_range": "Ha de ser un número de port vàlid (i.e. 0-65535) o un interval de ports (ex. 100:200)",
"pattern_positive_number": "Ha de ser un nombre positiu",
"pattern_username": "Ha d'estar compost per caràcters alfanumèrics en minúscula i guió baix exclusivament",
"pattern_password_app": "Les contrasenyes no haurien de tenir els següents caràcters: {forbidden_chars}",
"pattern_password_app": "Les contrasenyes no poden de tenir els següents caràcters: {forbidden_chars}",
"port_already_closed": "El port {port:d} ja està tancat per les connexions {ip_version:s}",
"port_already_opened": "El port {port:d} ja està obert per les connexions {ip_version:s}",
"port_available": "El port {port:d} està disponible",
"port_unavailable": "El port {port:d} no està disponible",
"recommend_to_add_first_user": "La post instal·lació s'ha acabat, però YunoHost necessita com a mínim un usuari per funcionar correctament, hauríeu d'afegir un usuari executant «yunohost user create $username» o amb la interfície d'administració.",
"recommend_to_add_first_user": "La post instal·lació s'ha acabat, però YunoHost necessita com a mínim un usuari per funcionar correctament, hauríeu d'afegir un usuari executant «yunohost user create <username>»; o fer-ho des de la interfície d'administració.",
"regenconf_file_backed_up": "S'ha guardat una còpia de seguretat del fitxer de configuració «{conf}» a «{backup}»",
"regenconf_file_copy_failed": "No s'ha pogut copiar el nou fitxer de configuració «{new}» a «{conf}»",
"regenconf_file_kept_back": "S'espera que el fitxer de configuració «{conf}» sigui suprimit per regen-conf (categoria {category}) però s'ha mantingut.",
@ -406,26 +406,26 @@
"regenconf_file_updated": "El fitxer de configuració «{conf}» ha estat actualitzat",
"regenconf_now_managed_by_yunohost": "El fitxer de configuració «{conf}» serà gestionat per YunoHost a partir d'ara (categoria {category}).",
"regenconf_up_to_date": "La configuració ja està al dia per la categoria «{category}»",
"regenconf_updated": "La configuració ha estat actualitzada per la categoria «{category}»",
"regenconf_updated": "La configuració per la categoria «{category}» ha estat actualitzada",
"regenconf_would_be_updated": "La configuració hagués estat actualitzada per la categoria «{category}»",
"regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»…",
"regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}",
"regenconf_pending_applying": "Aplicació de la configuració pendent per la categoria «{category}»…",
"restore_action_required": "S'ha d'especificar quelcom a restaurar",
"restore_already_installed_app": "Ja hi ha una aplicació instal·lada amb l'id «{app:s}»",
"restore_already_installed_app": "Una aplicació amb la ID «{app:s}» ja està instal·lada",
"restore_app_failed": "No s'ha pogut restaurar l'aplicació «{app:s}»",
"restore_cleaning_failed": "No s'ha pogut netejar el directori temporal de restauració",
"restore_complete": "Restauració completada",
"restore_confirm_yunohost_installed": "Esteu segur de voler restaurar un sistema ja instal·lat? [{answers:s}]",
"restore_extracting": "Extracció dels fitxers necessaris de l'arxiu…",
"restore_failed": "No s'ha pogut restaurar el sistema",
"restore_hook_unavailable": "L'script de restauració «{part:s}» no està disponible en el sistema i tampoc és en l'arxiu",
"restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el disc (espai lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)",
"restore_hook_unavailable": "El script de restauració «{part:s}» no està disponible en el sistema i tampoc és en l'arxiu",
"restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el sistema (lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)",
"restore_mounting_archive": "Muntatge de l'arxiu a «{path:s}»",
"restore_not_enough_disk_space": "No hi ha prou espai disponible en el disc (espai lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)",
"restore_not_enough_disk_space": "No hi ha prou espai disponible (espai: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)",
"restore_nothings_done": "No s'ha restaurat res",
"restore_removing_tmp_dir_failed": "No s'ha pogut eliminar un directori temporal antic",
"restore_running_app_script": "Execució de l'script de restauració de l'aplicació «{app:s}»…",
"restore_running_app_script": "Restaurant l'aplicació «{app:s}»…",
"restore_running_hooks": "Execució dels hooks de restauració…",
"restore_system_part_failed": "No s'ha pogut restaurar la part «{part:s}» del sistema",
"root_password_desynchronized": "S'ha canviat la contrasenya d'administració, però YunoHost no ha pogut propagar-ho cap a la contrasenya root!",
@ -439,24 +439,24 @@
"service_already_started": "Ja s'ha iniciat el servei «{service:s}»",
"service_already_stopped": "Ja s'ha aturat el servei «{service:s}»",
"service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command:s}»",
"service_description_avahi-daemon": "permet accedir al servidor via yunohost.local en la xarxa local",
"service_description_dnsmasq": "gestiona la resolució del nom de domini (DNS)",
"service_description_dovecot": "permet als clients de correu accedir/recuperar correus (via IMAP i POP3)",
"service_description_fail2ban": "protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet",
"service_description_glances": "monitora la informació del sistema en el servidor",
"service_description_metronome": "gestiona els comptes de missatgeria instantània XMPP",
"service_description_mysql": "guarda les dades de les aplicacions (base de dades SQL)",
"service_description_nginx": "serveix o permet l'accés a totes les pàgines web allotjades en el servidor",
"service_description_nslcd": "gestiona les connexions shell dels usuaris YunoHost",
"service_description_php7.0-fpm": "executa les aplicacions escrites en PHP amb nginx",
"service_description_postfix": "utilitzat per enviar i rebre correus",
"service_description_redis-server": "una base de dades especialitzada per l'accés ràpid a dades, files d'espera i comunicació entre programes",
"service_description_rmilter": "verifica diferents paràmetres en els correus",
"service_description_rspamd": "filtra el correu brossa, i altres funcionalitats relacionades al correu",
"service_description_slapd": "guarda el usuaris, dominis i informació relacionada",
"service_description_ssh": "permet la connexió remota al servidor via terminal (protocol SSH)",
"service_description_yunohost-api": "gestiona les interaccions entre la interfície web de YunoHost i el sistema",
"service_description_yunohost-firewall": "gestiona els ports de connexió oberts i tancats als serveis",
"service_description_avahi-daemon": "Permet accedir al servidor via «yunohost.local» en la xarxa local",
"service_description_dnsmasq": "Gestiona la resolució del nom de domini (DNS)",
"service_description_dovecot": "Permet als clients de correu accedir/recuperar correus (via IMAP i POP3)",
"service_description_fail2ban": "Protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet",
"service_description_glances": "Monitora la informació del sistema en el servidor",
"service_description_metronome": "Gestiona els comptes de missatgeria instantània XMPP",
"service_description_mysql": "Guarda les dades de les aplicacions (base de dades SQL)",
"service_description_nginx": "Serveix o permet l'accés a totes les pàgines web allotjades en el servidor",
"service_description_nslcd": "Gestiona les connexions shell dels usuaris YunoHost",
"service_description_php7.0-fpm": "Executa les aplicacions escrites en PHP amb NGINX",
"service_description_postfix": "Utilitzat per enviar i rebre correus",
"service_description_redis-server": "Una base de dades especialitzada per l'accés ràpid a dades, files d'espera i comunicació entre programes",
"service_description_rmilter": "Verifica diferents paràmetres en els correus",
"service_description_rspamd": "Filtra el correu brossa, i altres funcionalitats relacionades amb el correu",
"service_description_slapd": "Guarda el usuaris, dominis i informació relacionada",
"service_description_ssh": "Permet la connexió remota al servidor via terminal (protocol SSH)",
"service_description_yunohost-api": "Gestiona les interaccions entre la interfície web de YunoHost i el sistema",
"service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis",
"service_disable_failed": "No s'han pogut deshabilitar el servei «{service:s}»\n\nRegistres recents: {logs:s}",
"service_disabled": "S'ha deshabilitat el servei {service:s}",
"service_enable_failed": "No s'ha pogut activar el servei «{service:s}»\n\nRegistres recents: {log:s}",
@ -479,26 +479,26 @@
"service_unknown": "Servei «{service:s}» desconegut",
"ssowat_conf_generated": "S'ha generat la configuració SSOwat",
"ssowat_conf_updated": "S'ha actualitzat la configuració SSOwat",
"ssowat_persistent_conf_read_error": "Error en llegir la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON",
"ssowat_persistent_conf_write_error": "Error guardant la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON",
"ssowat_persistent_conf_read_error": "No s'ha pogut llegir la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON",
"ssowat_persistent_conf_write_error": "No s'ha pogut guardar la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON",
"system_upgraded": "S'ha actualitzat el sistema",
"system_username_exists": "El nom d'usuari ja existeix en els usuaris de sistema",
"this_action_broke_dpkg": "Aquesta acció a trencat dpkg/apt (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo dpkg --configure -a».",
"tools_upgrade_at_least_one": "Especifiqueu --apps O --system",
"system_username_exists": "El nom d'usuari ja existeix en la llista d'usuaris de sistema",
"this_action_broke_dpkg": "Aquesta acció a trencat dpkg/APT (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo dpkg --configure -a».",
"tools_upgrade_at_least_one": "Especifiqueu «--apps», o «--system»",
"tools_upgrade_cant_both": "No es poden actualitzar tant el sistema com les aplicacions al mateix temps",
"tools_upgrade_cant_hold_critical_packages": "No es poden mantenir els paquets crítics…",
"tools_upgrade_cant_unhold_critical_packages": "No es poden deixar de mantenir els paquets crítics…",
"tools_upgrade_regular_packages": "Actualitzant els paquets «normals» (no relacionats amb YunoHost)…",
"tools_upgrade_regular_packages_failed": "No s'han pogut actualitzar els paquets següents: {packages_list}",
"tools_upgrade_special_packages": "Actualitzant els paquets «especials» (relacionats amb YunoHost)…",
"tools_upgrade_special_packages_explanation": "Aquesta acció s'acabarà, però l'actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Un cop acabat, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines > Registres (a la interfície d'administració) o amb «yunohost log list» (a la línia d'ordres).",
"tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada!\nPremeu [Enter] per tornar a la línia d'ordres",
"tools_upgrade_special_packages_explanation": "Aquesta acció s'acabarà, però l'actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Un cop acabat, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o amb «yunohost log list» (des de la línia d'ordres).",
"tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada.\nPremeu [Enter] per tornar a la línia d'ordres",
"unbackup_app": "L'aplicació «{app:s}» no serà guardada",
"unexpected_error": "Hi ha hagut un error inesperat: {error}",
"unit_unknown": "Unitat desconeguda «{unit:s}»",
"unlimit": "Sense quota",
"unrestore_app": "L'aplicació «{app:s} no serà restaurada",
"update_apt_cache_failed": "No s'ha pogut actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}",
"update_apt_cache_failed": "No s'ha pogut actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list, que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}",
"update_apt_cache_warning": "Hi ha hagut errors al actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}",
"updating_apt_cache": "Obtenció de les actualitzacions disponibles per als paquets del sistema…",
"updating_app_lists": "Obtenció de les actualitzacions disponibles per a les aplicacions…",
@ -507,21 +507,21 @@
"upnp_dev_not_found": "No s'ha trobat cap dispositiu UPnP",
"upnp_disabled": "S'ha desactivat UPnP",
"upnp_enabled": "S'ha activat UPnP",
"upnp_port_open_failed": "No s'han pogut obrir els ports UPnP",
"upnp_port_open_failed": "No s'ha pogut obrir el port UPnP",
"user_created": "S'ha creat l'usuari",
"user_creation_failed": "No s'ha pogut crear l'usuari",
"user_creation_failed": "No s'ha pogut crear l'usuari {user}: {error}",
"user_deleted": "S'ha suprimit l'usuari",
"user_deletion_failed": "No s'ha pogut suprimir l'usuari",
"user_home_creation_failed": "No s'ha pogut crear la carpeta personal («home») de l'usuari",
"user_deletion_failed": "No s'ha pogut suprimir l'usuari {user}: {error}",
"user_home_creation_failed": "No s'ha pogut crear la carpeta personal «home» per l'usuari",
"user_info_failed": "No s'ha pogut obtenir la informació de l'usuari",
"user_unknown": "Usuari desconegut: {user:s}",
"user_update_failed": "No s'ha pogut actualitzar l'usuari",
"user_updated": "S'ha actualitzat l'usuari",
"user_update_failed": "No s'ha pogut actualitzar l'usuari {user}: {error}",
"user_updated": "S'ha canviat la informació de l'usuari",
"users_available": "Usuaris disponibles:",
"yunohost_already_installed": "YunoHost ja està instal·lat",
"yunohost_ca_creation_failed": "No s'ha pogut crear l'autoritat de certificació",
"yunohost_ca_creation_success": "S'ha creat l'autoritat de certificació local.",
"yunohost_configured": "S'ha configurat YunoHost",
"yunohost_configured": "YunoHost està configurat",
"yunohost_installing": "Instal·lació de YunoHost…",
"yunohost_not_installed": "YunoHost no està instal·lat o no està instal·lat correctament. Executeu «yunohost tools postinstall»",
"apps_permission_not_found": "No s'ha trobat cap permís per les aplicacions instal·lades",
@ -534,14 +534,14 @@
"group_already_disallowed": "El grup «{group:s}» ja té els permisos «{permission:s}» desactivats per l'aplicació «{app:s}»",
"group_name_already_exist": "El grup {name:s} ja existeix",
"group_created": "S'ha creat el grup «{group}»",
"group_creation_failed": "No s'ha pogut crear el grup «{group}»",
"group_creation_failed": "No s'ha pogut crear el grup «{group}»: {error}",
"group_deleted": "S'ha eliminat el grup «{group}»",
"group_deletion_failed": "No s'ha pogut eliminar el grup «{group}»",
"group_deletion_failed": "No s'ha pogut eliminar el grup «{group}»: {error}",
"group_deletion_not_allowed": "El grup {group:s} no es pot eliminar manualment.",
"group_info_failed": "Ha fallat la informació del grup",
"group_unknown": "Grup {group:s} desconegut",
"group_updated": "S'ha actualitzat el grup «{group}»",
"group_update_failed": "No s'ha pogut actualitzat el grup «{group}»",
"group_update_failed": "No s'ha pogut actualitzat el grup «{group}»: {error}",
"log_permission_add": "Afegir el permís «{}» per l'aplicació «{}»",
"log_permission_remove": "Suprimir el permís «{}»",
"log_permission_update": "Actualitzar el permís «{}» per l'aplicació «{}»",
@ -550,13 +550,13 @@
"log_user_group_update": "Actualitzar grup «{}»",
"log_user_permission_add": "Actualitzar el permís «{}»",
"log_user_permission_remove": "Actualitzar el permís «{}»",
"mailbox_disabled": "La bústia de correu està desactivada per als usuaris {user:s}",
"mailbox_disabled": "La bústia de correu està desactivada per al usuari {user:s}",
"migration_description_0011_setup_group_permission": "Configurar el grup d'usuaris i els permisos per les aplicacions i els serveis",
"migration_0011_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i la configuració de les aplicacions abans d'efectuar la migració.",
"migration_0011_can_not_backup_before_migration": "No s'ha pogut fer la còpia de seguretat abans de la migració. No s'ha pogut fer la migració. Error: {error:s}",
"migration_0011_create_group": "Creant un grup per a cada usuari…",
"migration_0011_done": "Migració completa. Ja podeu gestionar grups d'usuaris.",
"migration_0011_LDAP_config_dirty": "Sembla que heu modificat manualment la configuració LDAP. Per fer aquesta migració s'ha d'actualitzar la configuració LDAP.\nGuardeu la configuració actual, reinicieu la configuració original amb l'ordre «yunohost tools regen-conf -f» i torneu a intentar la migració",
"migration_0011_LDAP_config_dirty": "Sembla que heu modificat manualment la configuració LDAP. Per fer aquesta migració s'ha d'actualitzar la configuració LDAP.\nGuardeu la configuració actual, reinicieu la configuració original executant l'ordre «yunohost tools regen-conf -f» i torneu a intentar la migració",
"migration_0011_LDAP_update_failed": "Ha fallat l'actualització de LDAP. Error: {error:s}",
"migration_0011_migrate_permission": "Fent la migració dels permisos de la configuració de les aplicacions a LDAP…",
"migration_0011_migration_failed_trying_to_rollback": "La migració ha fallat… s'intenta tornar el sistema a l'estat anterior.",
@ -565,16 +565,16 @@
"migration_0011_update_LDAP_schema": "Actualitzant l'esquema LDAP…",
"need_define_permission_before": "Heu de tornar a redefinir els permisos utilitzant «yunohost user permission add -u USER» abans d'eliminar un grup permès",
"permission_already_clear": "Ja s'ha donat el permís «{permission:s}» per l'aplicació {app:s}",
"permission_already_exist": "El permís «{permission:s}» ja existeix per l'aplicació {app:s}",
"permission_created": "S'ha creat el permís «{permission:s}» per l'aplicació {app:s}",
"permission_creation_failed": "La creació del permís ha fallat",
"permission_deleted": "S'ha eliminat el permís «{permission:s}» per l'aplicació {app:s}",
"permission_deletion_failed": "L'eliminació del permís «{permission:s}» per l'aplicació {app:s} ha fallat",
"permission_not_found": "No s'ha trobat el permís «{permission:s}» per l'aplicació {app:s}",
"permission_already_exist": "El permís «{permission:s}» ja existeix",
"permission_created": "S'ha creat el permís «{permission:s}»",
"permission_creation_failed": "No s'ha pogut crear el permís «{permission}»: {error}",
"permission_deleted": "S'ha eliminat el permís «{permission:s}»",
"permission_deletion_failed": "No s'ha pogut eliminar el permís «{permission:s}»: {error}",
"permission_not_found": "No s'ha trobat el permís «{permission:s}»",
"permission_name_not_valid": "El nom del permís «{permission:s}» no és vàlid",
"permission_update_failed": "L'actualització del permís ha fallat",
"permission_update_failed": "No s'ha pogut actualitzar el permís «{permission}»: {error}",
"permission_generated": "S'ha actualitzat la base de dades del permís",
"permission_updated": "S'ha actualitzat el permís «{permission:s}» per l'aplicació {app:s}",
"permission_updated": "S'ha actualitzat el permís «{permission:s}»",
"permission_update_nothing_to_do": "No hi ha cap permís per actualitzar",
"remove_main_permission_not_allowed": "No es pot eliminar el permís principal",
"remove_user_of_group_not_allowed": "No es pot eliminar l'usuari {user:s} del grup {group:s}",
@ -582,5 +582,40 @@
"tools_update_failed_to_app_fetchlist": "No s'ha pogut actualitzar la llista d'aplicacions de YunoHost a causa de: {error}",
"user_already_in_group": "L'usuari {user:s} ja és en el grup {group:s}",
"user_not_in_group": "L'usuari {user:s} no és en el grup {group:s}",
"migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació postgresql a fer servir md5 per a les connexions locals"
"migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació PostgreSQL a fer servir MD5 per a les connexions locals",
"app_full_domain_unavailable": "Aquesta aplicació requereix un domini sencer per ser instal·lada, però ja hi ha altres aplicacions instal·lades al domini «{domain}». Una possible solució és afegir i utilitzar un subdomini dedicat a aquesta aplicació.",
"migrations_not_pending_cant_skip": "Aquestes migracions no estan pendents, així que no poden ser omeses: {ids}",
"app_action_broke_system": "Aquesta acció sembla haver trencat els següents serveis importants: {services}",
"log_permission_urls": "Actualitzar les URLs relacionades amb el permís «{}»",
"log_user_group_create": "Crear grup «{}»",
"log_user_permission_update": "Actualitzar els accessos per al permís «{}»",
"log_user_permission_reset": "Restablir el permís «{}»",
"permission_already_disallowed": "El grup «{group}» ja té el permís «{permission}» desactivat",
"migrations_already_ran": "Aquestes migracions ja s'han fet: {ids}",
"migrations_dependencies_not_satisfied": "No s'ha pogut executar la migració {id} perquè s'han d'executar primer les següents migracions: {dependencies_id}",
"migrations_failed_to_load_migration": "No s'ha pogut carregar la migració {id}: {error}",
"migrations_exclusive_options": "«--auto», «--skip», i «--force-rerun» són opcions mútuament excloents.",
"migrations_must_provide_explicit_targets": "Heu de proporcionar objectius explícits al utilitzar «--skip» o «--force-rerun»",
"migrations_no_such_migration": "No hi ha cap migració anomenada {id}",
"migrations_pending_cant_rerun": "Aquestes migracions encara estan pendents, així que no es poden tornar a executar: {ids}",
"migrations_running_forward": "Executant la migració {id}…",
"migrations_success_forward": "Migració {id} completada",
"apps_already_up_to_date": "Ja estan actualitzades totes les aplicacions",
"dyndns_provider_unreachable": "No s'ha pogut connectar amb el proveïdor Dyndns {provider}: o el vostre YunoHost no està ben connectat a Internet o el servidor dynette està caigut.",
"operation_interrupted": "S'ha interromput manualment l'operació?",
"group_already_exist": "El grup {group} ja existeix",
"group_already_exist_on_system": "El grup {group} ja existeix en els grups del sistema",
"group_cannot_be_edited": "El grup {group} no es pot editar manualment.",
"group_cannot_be_deleted": "El grup {group} no es pot eliminar manualment.",
"group_user_already_in_group": "L'usuari {user} ja està en el grup {group}",
"group_user_not_in_group": "L'usuari {user} no està en el grup {group}",
"log_permission_create": "Crear el permís «{}»",
"log_permission_delete": "Eliminar el permís «{}»",
"migration_0011_failed_to_remove_stale_object": "No s'ha pogut eliminar l'objecte obsolet {dn}: {error}",
"permission_already_allowed": "El grup «{group}» ja té el permís «{permission}» activat",
"permission_cannot_remove_main": "No es permet eliminar un permís principal",
"user_already_exists": "L'usuari {user} ja existeix",
"app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir els possibles danys ja que hi ha hagut un error en l'actualització de l'anterior aplicació",
"app_install_failed": "No s'ha pogut instal·lar {app}",
"app_install_script_failed": "Hi ha hagut un error en el script d'instal·lació de l'aplicació"
}

View file

@ -413,5 +413,6 @@
"global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts",
"global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst",
"log_app_makedefault": "Mache '{}' zur Standard-Anwendung",
"hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}"
"hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}",
"app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation einer vollständigen Domäne, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist."
}

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 could not be completed before the migration failed. Error: {error:s}",
"migration_0011_create_group": "Creating a group for each user…",
"migration_0011_done": "Migration completed. You are now able to manage usergroups.",
"migration_0011_LDAP_config_dirty": "Save your current custom LDAP conguration, and reintialize the original one 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": "Could not migrate… 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",
@ -508,8 +514,6 @@
"service_unknown": "Unknown service '{service:s}'",
"ssowat_conf_generated": "SSOwat configuration generated",
"ssowat_conf_updated": "SSOwat configuration updated",
"ssowat_persistent_conf_read_error": "Could not read persistent SSOwat configuration: {error:s}. Edit the /etc/ssowat/conf.json.persistent file to fix the JSON syntax",
"ssowat_persistent_conf_write_error": "Could not save persistent SSOwat configuration: {error:s}. Edit the /etc/ssowat/conf.json.persistent file to fix the JSON syntax",
"system_upgraded": "System upgraded",
"system_username_exists": "Username already exists in the list of system users",
"this_action_broke_dpkg": "This action broke dpkg/APT (the system package managers)… You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.",

View file

@ -626,5 +626,7 @@
"permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado",
"permission_cannot_remove_main": "No está permitido eliminar un permiso principal",
"user_already_exists": "El usuario {user} ya existe",
"app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación."
"app_full_domain_unavailable": "Lamentablemente esta aplicación necesita un dominio completo para ser instalada pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Una solución posible es añadir y usar un subdominio dedicado a esta aplicación.",
"app_install_failed": "No se pudo instalar {app}",
"app_install_script_failed": "Ha ocurrido un error en el guión de instalación de la aplicación"
}

View file

@ -41,7 +41,7 @@ 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
from moulinette.utils.filesystem import read_json, read_toml, read_yaml, write_to_json
from yunohost.service import service_log, service_status, _run_service_command
from yunohost.utils import packages
@ -427,23 +427,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"]:
# 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']
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[domain] = {}
result[domain][path] = {
'label': app_settings['label'],
'id': app_settings['id']
result[perm_domain] = {}
result[perm_domain][perm_path] = {
'label': perm_label,
'id': app_id
}
else:
result[domain + path] = app_settings['label']
result[perm_domain + perm_path] = perm_label
return result
@ -461,7 +523,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 +612,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 +822,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 +979,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 +1094,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 +1202,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 +1223,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 +1243,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")
@ -1233,25 +1302,21 @@ def app_makedefault(operation_logger, app, domain=None):
raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain,
other_app=app_map(raw=True)[domain]["/"]["id"])
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise YunohostError('ssowat_persistent_conf_read_error', error=e)
except IOError:
# TODO / FIXME : current trick is to add this to conf.json.persisten
# This is really not robust and should be improved
# e.g. have a flag in /etc/yunohost/apps/$app/ to say that this is the
# default app or idk...
if not os.path.exists('/etc/ssowat/conf.json.persistent'):
ssowat_conf = {}
else:
ssowat_conf = read_json('/etc/ssowat/conf.json.persistent')
if 'redirected_urls' not in ssowat_conf:
ssowat_conf['redirected_urls'] = {}
ssowat_conf['redirected_urls'][domain + '/'] = app_domain + app_path
try:
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
except IOError as e:
raise YunohostError('ssowat_persistent_conf_write_error', error=e)
write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf)
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
logger.success(m18n.n('ssowat_conf_updated'))
@ -1283,6 +1348,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)
@ -1447,6 +1514,7 @@ def app_ssowatconf():
main_domain = _get_maindomain()
domains = domain_list()['domains']
all_permissions = user_permission_list(full=True)['permissions']
skipped_urls = []
skipped_regex = []
@ -1468,34 +1536,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)
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
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'])
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
# 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'])
@ -1504,10 +1608,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,
@ -2687,10 +2795,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

@ -602,10 +602,10 @@ class BackupManager():
env=env_dict,
chdir=self.work_dir)
ret_succeed = {hook: {path:result["state"] for path, result in infos.items()}
ret_succeed = {hook: [path for path, result in infos.items() if result["state"] == "succeed"]
for hook, infos in ret.items()
if any(result["state"] == "succeed" for result in infos.values())}
ret_failed = {hook: {path:result["state"] for path, result in infos.items.items()}
ret_failed = {hook: [path for path, result in infos.items.items() if result["state"] == "failed"]
for hook, infos in ret.items()
if any(result["state"] == "failed" for result in infos.values())}
@ -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:
@ -2371,6 +2373,13 @@ def backup_info(name, with_details=False, human_readable=False):
if "size_details" in info.keys():
for category in ["apps", "system"]:
for name, key_info in info[category].items():
# Stupid legacy fix for weird format between 3.5 and 3.6
if isinstance(key_info, dict):
key_info = key_info.keys()
info[category][name] = key_info = {"paths": key_info}
if name in info["size_details"][category].keys():
key_info["size"] = info["size_details"][category][name]
if human_readable:

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

@ -1,9 +1,32 @@
import pytest
import sys
import moulinette
from moulinette import m18n
from yunohost.utils.error import YunohostError
from contextlib import contextmanager
sys.path.append("..")
@contextmanager
def message(mocker, key, **kwargs):
mocker.spy(m18n, "n")
yield
m18n.n.assert_any_call(key, **kwargs)
@contextmanager
def raiseYunohostError(mocker, key, **kwargs):
with pytest.raises(YunohostError) as e_info:
yield
assert e_info._excinfo[1].key == key
if kwargs:
assert e_info._excinfo[1].kwargs == kwargs
def pytest_addoption(parser):
parser.addoption("--yunodebug", action="store_true", default=False)

View file

@ -4,6 +4,8 @@ import pytest
import shutil
import requests
from conftest import message, raiseYunohostError
from moulinette import m18n
from moulinette.utils.filesystem import mkdir
@ -113,16 +115,16 @@ def app_is_not_installed(domain, app):
def app_is_exposed_on_http(domain, path, message_in_page):
try:
r = requests.get("http://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10)
r = requests.get("http://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10, verify=False)
return r.status_code == 200 and message_in_page in r.text
except Exception:
except Exception as e:
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 +182,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")
@ -196,11 +192,11 @@ def test_legacy_app_install_private(secondary_domain):
assert app_is_not_installed(secondary_domain, "legacy_app")
def test_legacy_app_install_unknown_domain():
def test_legacy_app_install_unknown_domain(mocker):
with pytest.raises(YunohostError):
with message(mocker, "app_argument_invalid"):
install_legacy_app("whatever.nope", "/legacy")
# TODO check error message
assert app_is_not_installed("whatever.nope", "legacy_app")
@ -227,55 +223,51 @@ def test_legacy_app_install_multiple_instances(secondary_domain):
assert app_is_not_installed(secondary_domain, "legacy_app__2")
def test_legacy_app_install_path_unavailable(secondary_domain):
def test_legacy_app_install_path_unavailable(mocker, secondary_domain):
# These will be removed in teardown
install_legacy_app(secondary_domain, "/legacy")
with pytest.raises(YunohostError):
with message(mocker, "app_location_unavailable"):
install_legacy_app(secondary_domain, "/")
# TODO check error message
assert app_is_installed(secondary_domain, "legacy_app")
assert app_is_not_installed(secondary_domain, "legacy_app__2")
def test_legacy_app_install_bad_args():
with pytest.raises(YunohostError):
install_legacy_app("this.domain.does.not.exists", "/legacy")
def test_legacy_app_install_with_nginx_down(secondary_domain):
def test_legacy_app_install_with_nginx_down(mocker, secondary_domain):
os.system("systemctl stop nginx")
with pytest.raises(YunohostError):
with raiseYunohostError(mocker, "app_action_cannot_be_ran_because_required_services_down"):
install_legacy_app(secondary_domain, "/legacy")
def test_legacy_app_failed_install(secondary_domain):
def test_legacy_app_failed_install(mocker, secondary_domain):
# This will conflict with the folder that the app
# attempts to create, making the install fail
mkdir("/var/www/legacy_app/", 0o750)
with pytest.raises(YunohostError):
with message(mocker, 'app_install_script_failed'):
install_legacy_app(secondary_domain, "/legacy")
# TODO check error message
assert app_is_not_installed(secondary_domain, "legacy_app")
def test_legacy_app_failed_remove(secondary_domain):
def test_legacy_app_failed_remove(mocker, secondary_domain):
install_legacy_app(secondary_domain, "/legacy")
# The remove script runs with set -eu and attempt to remove this
# file without -f, so will fail if it's not there ;)
os.remove("/etc/nginx/conf.d/%s.d/%s.conf" % (secondary_domain, "legacy_app"))
with pytest.raises(YunohostError):
app_remove("legacy")
# TODO / FIXME : can't easily validate that 'app_not_properly_removed'
# is triggered for weird reasons ...
app_remove("legacy_app")
#
# Well here, we hit the classical issue where if an app removal script
@ -292,59 +284,61 @@ def test_full_domain_app(secondary_domain):
assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app")
def test_full_domain_app_with_conflicts(secondary_domain):
def test_full_domain_app_with_conflicts(mocker, secondary_domain):
install_legacy_app(secondary_domain, "/legacy")
# TODO : once #808 is merged, add test that the message raised is 'app_full_domain_unavailable'
with pytest.raises(YunohostError):
with raiseYunohostError(mocker, "app_full_domain_unavailable"):
install_full_domain_app(secondary_domain)
def test_systemfuckedup_during_app_install(secondary_domain):
def test_systemfuckedup_during_app_install(mocker, secondary_domain):
with pytest.raises(YunohostError):
with message(mocker, "app_install_failed"):
with message(mocker, 'app_action_broke_system'):
install_break_yo_system(secondary_domain, breakwhat="install")
os.system("nginx -t")
os.system("systemctl status nginx")
assert app_is_not_installed(secondary_domain, "break_yo_system")
def test_systemfuckedup_during_app_remove(secondary_domain):
def test_systemfuckedup_during_app_remove(mocker, secondary_domain):
install_break_yo_system(secondary_domain, breakwhat="remove")
with pytest.raises(YunohostError):
with message(mocker, 'app_action_broke_system'):
with message(mocker, 'app_removed'):
app_remove("break_yo_system")
os.system("nginx -t")
os.system("systemctl status nginx")
assert app_is_not_installed(secondary_domain, "break_yo_system")
def test_systemfuckedup_during_app_install_and_remove(secondary_domain):
def test_systemfuckedup_during_app_install_and_remove(mocker, secondary_domain):
with pytest.raises(YunohostError):
with message(mocker, "app_install_failed"):
with message(mocker, 'app_action_broke_system'):
install_break_yo_system(secondary_domain, breakwhat="everything")
assert app_is_not_installed(secondary_domain, "break_yo_system")
def test_systemfuckedup_during_app_upgrade(secondary_domain):
def test_systemfuckedup_during_app_upgrade(mocker, secondary_domain):
install_break_yo_system(secondary_domain, breakwhat="upgrade")
with pytest.raises(YunohostError):
with message(mocker, 'app_action_broke_system'):
app_upgrade("break_yo_system", file="./tests/apps/break_yo_system_ynh")
def test_failed_multiple_app_upgrade(secondary_domain):
def test_failed_multiple_app_upgrade(mocker, secondary_domain):
install_legacy_app(secondary_domain, "/legacy")
install_break_yo_system(secondary_domain, breakwhat="upgrade")
with pytest.raises(YunohostError):
with raiseYunohostError(mocker, 'app_not_upgraded'):
app_upgrade(["break_yo_system", "legacy_app"],
file={"break_yo_system": "./tests/apps/break_yo_system_ynh",
"legacy": "./tests/apps/legacy_app_ynh"})

View file

@ -2,14 +2,13 @@ import pytest
import os
import shutil
import subprocess
from mock import ANY
from moulinette import m18n
from conftest import message, raiseYunohostError
from yunohost.app import app_install, app_remove, app_ssowatconf
from yunohost.app import _is_installed
from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount
from yunohost.domain import _get_maindomain
from yunohost.utils.error import YunohostError
from yunohost.user import user_permission_list, user_create, user_list, user_delete
from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps
@ -206,9 +205,10 @@ def add_archive_system_from_2p4():
#
def test_backup_only_ldap():
def test_backup_only_ldap(mocker):
# Create the backup
with message(mocker, "backup_created"):
backup_create(system=["conf_ldap"], apps=None)
archives = backup_list()["archives"]
@ -222,23 +222,21 @@ def test_backup_only_ldap():
def test_backup_system_part_that_does_not_exists(mocker):
mocker.spy(m18n, "n")
# Create the backup
with pytest.raises(YunohostError):
backup_create(system=["yolol"], apps=None)
with message(mocker, 'backup_hook_unknown', hook="doesnt_exist"):
with raiseYunohostError(mocker, "backup_nothings_done"):
backup_create(system=["doesnt_exist"], apps=None)
m18n.n.assert_any_call('backup_hook_unknown', hook="yolol")
m18n.n.assert_any_call('backup_nothings_done')
#
# System backup and restore #
#
def test_backup_and_restore_all_sys():
def test_backup_and_restore_all_sys(mocker):
# Create the backup
with message(mocker, "backup_created"):
backup_create(system=[], apps=None)
archives = backup_list()["archives"]
@ -255,6 +253,7 @@ def test_backup_and_restore_all_sys():
assert not os.path.exists("/etc/ssowat/conf.json")
# Restore the backup
with message(mocker, "restore_complete"):
backup_restore(name=archives[0], force=True,
system=[], apps=None)
@ -270,12 +269,14 @@ def test_backup_and_restore_all_sys():
def test_restore_system_from_Ynh2p4(monkeypatch, mocker):
# Backup current system
with message(mocker, "backup_created"):
backup_create(system=[], apps=None)
archives = backup_list()["archives"]
assert len(archives) == 2
# Restore system archive from 2.4
try:
with message(mocker, "restore_complete"):
backup_restore(name=backup_list()["archives"][1],
system=[],
apps=None,
@ -306,13 +307,11 @@ def test_backup_script_failure_handling(monkeypatch, mocker):
# call with monkeypatch). We also patch m18n to check later it's been called
# with the expected error message key
monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec)
mocker.spy(m18n, "n")
with pytest.raises(YunohostError):
with message(mocker, 'backup_app_failed', app='backup_recommended_app'):
with raiseYunohostError(mocker, 'backup_nothings_done'):
backup_create(system=None, apps=["backup_recommended_app"])
m18n.n.assert_any_call('backup_app_failed', app='backup_recommended_app')
@pytest.mark.with_backup_recommended_app_installed
def test_backup_not_enough_free_space(monkeypatch, mocker):
@ -327,26 +326,18 @@ def test_backup_not_enough_free_space(monkeypatch, mocker):
monkeypatch.setattr("yunohost.backup.free_space_in_directory",
custom_free_space_in_directory)
mocker.spy(m18n, "n")
with pytest.raises(YunohostError):
with raiseYunohostError(mocker, 'not_enough_disk_space'):
backup_create(system=None, apps=["backup_recommended_app"])
m18n.n.assert_any_call('not_enough_disk_space', path=ANY)
def test_backup_app_not_installed(mocker):
assert not _is_installed("wordpress")
mocker.spy(m18n, "n")
with pytest.raises(YunohostError):
with message(mocker, "unbackup_app", app="wordpress"):
with raiseYunohostError(mocker, 'backup_nothings_done'):
backup_create(system=None, apps=["wordpress"])
m18n.n.assert_any_call("unbackup_app", app="wordpress")
m18n.n.assert_any_call('backup_nothings_done')
@pytest.mark.with_backup_recommended_app_installed
def test_backup_app_with_no_backup_script(mocker):
@ -355,14 +346,10 @@ def test_backup_app_with_no_backup_script(mocker):
os.system("rm %s" % backup_script)
assert not os.path.exists(backup_script)
mocker.spy(m18n, "n")
with pytest.raises(YunohostError):
with message(mocker, "backup_with_no_backup_script_for_app", app="backup_recommended_app"):
with raiseYunohostError(mocker, 'backup_nothings_done'):
backup_create(system=None, apps=["backup_recommended_app"])
m18n.n.assert_any_call("backup_with_no_backup_script_for_app", app="backup_recommended_app")
m18n.n.assert_any_call('backup_nothings_done')
@pytest.mark.with_backup_recommended_app_installed
def test_backup_app_with_no_restore_script(mocker):
@ -371,20 +358,18 @@ def test_backup_app_with_no_restore_script(mocker):
os.system("rm %s" % restore_script)
assert not os.path.exists(restore_script)
mocker.spy(m18n, "n")
# Backuping an app with no restore script will only display a warning to the
# user...
with message(mocker, "backup_with_no_restore_script_for_app", app="backup_recommended_app"):
backup_create(system=None, apps=["backup_recommended_app"])
m18n.n.assert_any_call("backup_with_no_restore_script_for_app", app="backup_recommended_app")
@pytest.mark.clean_opt_dir
def test_backup_with_different_output_directory():
def test_backup_with_different_output_directory(mocker):
# Create the backup
with message(mocker, "backup_created"):
backup_create(system=["conf_ssh"], apps=None,
output_directory="/opt/test_backup_output_directory",
name="backup")
@ -401,8 +386,10 @@ def test_backup_with_different_output_directory():
@pytest.mark.clean_opt_dir
def test_backup_with_no_compress():
def test_backup_with_no_compress(mocker):
# Create the backup
with message(mocker, "backup_created"):
backup_create(system=["conf_nginx"], apps=None,
output_directory="/opt/test_backup_output_directory",
no_compress=True,
@ -416,8 +403,9 @@ def test_backup_with_no_compress():
#
@pytest.mark.with_wordpress_archive_from_2p4
def test_restore_app_wordpress_from_Ynh2p4():
def test_restore_app_wordpress_from_Ynh2p4(mocker):
with message(mocker, "restore_complete"):
backup_restore(system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
@ -431,16 +419,14 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker):
raise Exception
monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec)
mocker.spy(m18n, "n")
assert not _is_installed("wordpress")
with pytest.raises(YunohostError):
with message(mocker, 'restore_app_failed', app='wordpress'):
with raiseYunohostError(mocker, 'restore_nothings_done'):
backup_restore(system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
m18n.n.assert_any_call('restore_app_failed', app='wordpress')
m18n.n.assert_any_call('restore_nothings_done')
assert not _is_installed("wordpress")
@ -452,18 +438,13 @@ def test_restore_app_not_enough_free_space(monkeypatch, mocker):
monkeypatch.setattr("yunohost.backup.free_space_in_directory",
custom_free_space_in_directory)
mocker.spy(m18n, "n")
assert not _is_installed("wordpress")
with pytest.raises(YunohostError):
with raiseYunohostError(mocker, 'restore_not_enough_disk_space'):
backup_restore(system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
m18n.n.assert_any_call('restore_not_enough_disk_space',
free_space=0,
margin=ANY,
needed_space=ANY)
assert not _is_installed("wordpress")
@ -473,13 +454,11 @@ def test_restore_app_not_in_backup(mocker):
assert not _is_installed("wordpress")
assert not _is_installed("yoloswag")
mocker.spy(m18n, "n")
with pytest.raises(YunohostError):
with message(mocker, 'backup_archive_app_not_found', app="yoloswag"):
with raiseYunohostError(mocker, 'restore_nothings_done'):
backup_restore(system=None, name=backup_list()["archives"][0],
apps=["yoloswag"])
m18n.n.assert_any_call('backup_archive_app_not_found', app="yoloswag")
assert not _is_installed("wordpress")
assert not _is_installed("yoloswag")
@ -489,38 +468,36 @@ def test_restore_app_already_installed(mocker):
assert not _is_installed("wordpress")
with message(mocker, "restore_complete"):
backup_restore(system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
assert _is_installed("wordpress")
mocker.spy(m18n, "n")
with pytest.raises(YunohostError):
with message(mocker, 'restore_already_installed_app', app="wordpress"):
with raiseYunohostError(mocker, 'restore_nothings_done'):
backup_restore(system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
m18n.n.assert_any_call('restore_already_installed_app', app="wordpress")
m18n.n.assert_any_call('restore_nothings_done')
assert _is_installed("wordpress")
@pytest.mark.with_legacy_app_installed
def test_backup_and_restore_legacy_app():
def test_backup_and_restore_legacy_app(mocker):
_test_backup_and_restore_app("legacy_app")
_test_backup_and_restore_app(mocker, "legacy_app")
@pytest.mark.with_backup_recommended_app_installed
def test_backup_and_restore_recommended_app():
def test_backup_and_restore_recommended_app(mocker):
_test_backup_and_restore_app("backup_recommended_app")
_test_backup_and_restore_app(mocker, "backup_recommended_app")
@pytest.mark.with_backup_recommended_app_installed_with_ynh_restore
def test_backup_and_restore_with_ynh_restore():
def test_backup_and_restore_with_ynh_restore(mocker):
_test_backup_and_restore_app("backup_recommended_app")
_test_backup_and_restore_app(mocker, "backup_recommended_app")
@pytest.mark.with_permission_app_installed
def test_backup_and_restore_permission_app():
@ -529,11 +506,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,18 +520,19 @@ 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'] == []
def _test_backup_and_restore_app(app):
def _test_backup_and_restore_app(mocker, app):
# Create a backup of this app
with message(mocker, "backup_created"):
backup_create(system=None, apps=[app])
archives = backup_list()["archives"]
@ -571,6 +549,7 @@ def _test_backup_and_restore_app(app):
assert app+".main" not in user_permission_list()['permissions']
# Restore the app
with message(mocker, "restore_complete"):
backup_restore(system=None, name=archives[0],
apps=[app])
@ -593,13 +572,11 @@ def test_restore_archive_with_no_json(mocker):
assert "badbackup" in backup_list()["archives"]
mocker.spy(m18n, "n")
with pytest.raises(YunohostError):
with raiseYunohostError(mocker, 'backup_invalid_archive'):
backup_restore(name="badbackup", force=True)
m18n.n.assert_any_call('backup_invalid_archive')
def test_backup_binds_are_readonly(monkeypatch):
def test_backup_binds_are_readonly(mocker, monkeypatch):
def custom_mount_and_backup(self, backup_manager):
self.manager = backup_manager
@ -620,4 +597,5 @@ def test_backup_binds_are_readonly(monkeypatch):
custom_mount_and_backup)
# Create the backup
with message(mocker, "backup_created"):
backup_create(system=[])

View file

@ -1,44 +1,54 @@
import requests
import pytest
from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map
from conftest import message, raiseYunohostError
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.app import app_install, app_remove, app_change_url, app_list, app_map
from yunohost.user import user_list, user_create, user_delete, \
user_group_list, user_group_delete
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']:
if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]):
permission_delete(p, force=True, sync_perm=False)
def setup_function(function):
clean_user_groups_permission()
user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh")
user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh")
permission_create("wiki.main", 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")
def teardown_function(function):
clean_user_groups_permission()
try:
app_remove("permissions_app")
except:
pass
try:
app_remove("legacy_app")
except:
pass
@pytest.fixture(autouse=True)
def check_LDAP_db_integrity_call():
@ -46,6 +56,7 @@ def check_LDAP_db_integrity_call():
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:
@ -156,6 +167,32 @@ 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,13 +208,15 @@ 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
#
def test_permission_create_main():
def test_permission_create_main(mocker):
with message(mocker, "permission_created", permission="site.main"):
permission_create("site.main")
res = user_permission_list(full=True)['permissions']
@ -186,7 +225,8 @@ def test_permission_create_main():
assert set(res['site.main']['corresponding_users']) == set(["alice", "bob"])
def test_permission_create_extra():
def test_permission_create_extra(mocker):
with message(mocker, "permission_created", permission="site.test"):
permission_create("site.test")
res = user_permission_list(full=True)['permissions']
@ -195,7 +235,17 @@ def test_permission_create_extra():
assert "all_users" not in res['site.test']['allowed']
assert res['site.test']['corresponding_users'] == []
def test_permission_delete():
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(mocker):
with message(mocker, "permission_deleted", permission="wiki.main"):
permission_delete("wiki.main", force=True)
res = user_permission_list()['permissions']
@ -205,12 +255,14 @@ def test_permission_delete():
# Error on create - remove function
#
def test_permission_create_already_existing():
with pytest.raises(YunohostError):
def test_permission_create_already_existing(mocker):
with raiseYunohostError(mocker, "permission_already_exist"):
permission_create("wiki.main")
def test_permission_delete_doesnt_existing():
with pytest.raises(YunohostError):
def test_permission_delete_doesnt_existing(mocker):
with raiseYunohostError(mocker, "permission_not_found"):
permission_delete("doesnt.exist", force=True)
res = user_permission_list()['permissions']
@ -219,8 +271,9 @@ def test_permission_delete_doesnt_existing():
assert "mail.main" in res
assert "xmpp.main" in res
def test_permission_delete_main_without_force():
with pytest.raises(YunohostError):
def test_permission_delete_main_without_force(mocker):
with raiseYunohostError(mocker, "permission_cannot_remove_main"):
permission_delete("blog.main")
res = user_permission_list()['permissions']
@ -232,116 +285,130 @@ def test_permission_delete_main_without_force():
# user side functions
def test_permission_add_group():
def test_permission_add_group(mocker):
with message(mocker, "permission_updated", permission="wiki.main"):
user_permission_update("wiki.main", add="alice")
res = user_permission_list(full=True)['permissions']
assert set(res['wiki.main']['allowed']) == set(["all_users", "alice"])
assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"])
def test_permission_remove_group():
def test_permission_remove_group(mocker):
with message(mocker, "permission_updated", permission="blog.main"):
user_permission_update("blog.main", remove="alice")
res = user_permission_list(full=True)['permissions']
assert res['blog.main']['allowed'] == []
assert res['blog.main']['corresponding_users'] == []
def test_permission_add_and_remove_group():
def test_permission_add_and_remove_group(mocker):
with message(mocker, "permission_updated", permission="wiki.main"):
user_permission_update("wiki.main", add="alice", remove="all_users")
res = user_permission_list(full=True)['permissions']
assert res['wiki.main']['allowed'] == ["alice"]
assert res['wiki.main']['corresponding_users'] == ["alice"]
def test_permission_add_group_already_allowed():
def test_permission_add_group_already_allowed(mocker):
with message(mocker, "permission_already_allowed", permission="blog.main", group="alice"):
user_permission_update("blog.main", add="alice")
res = user_permission_list(full=True)['permissions']
assert res['blog.main']['allowed'] == ["alice"]
assert res['blog.main']['corresponding_users'] == ["alice"]
def test_permission_remove_group_already_not_allowed():
def test_permission_remove_group_already_not_allowed(mocker):
with message(mocker, "permission_already_disallowed", permission="blog.main", group="bob"):
user_permission_update("blog.main", remove="bob")
res = user_permission_list(full=True)['permissions']
assert res['blog.main']['allowed'] == ["alice"]
assert res['blog.main']['corresponding_users'] == ["alice"]
def test_permission_reset():
# Reset permission
def test_permission_reset(mocker):
with message(mocker, "permission_updated", permission="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"])
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"])
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
#
def test_permission_add_group_that_doesnt_exist():
with pytest.raises(YunohostError):
def test_permission_add_group_that_doesnt_exist(mocker):
with raiseYunohostError(mocker, "group_unknown"):
user_permission_update("blog.main", add="doesnt_exist")
res = user_permission_list(full=True)['permissions']
assert res['blog.main']['allowed'] == ["alice"]
assert res['blog.main']['corresponding_users'] == ["alice"]
def test_permission_update_permission_that_doesnt_exist():
with pytest.raises(YunohostError):
def test_permission_update_permission_that_doesnt_exist(mocker):
with raiseYunohostError(mocker, "permission_not_found"):
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
#
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,25 +428,79 @@ 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
res = user_permission_list(full=True)['permissions']
assert not any(p.startswith("permissions_app.") for p in res.keys())
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

@ -1,22 +1,25 @@
import pytest
from conftest import message, raiseYunohostError
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
user_group_list, user_group_create, user_group_delete, user_group_update
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()
def clean_user_groups():
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)
def setup_function(function):
clean_user_groups()
@ -29,9 +32,11 @@ def setup_function(function):
user_group_update("dev", add=["alice"])
user_group_update("apps", add=["bob"])
def teardown_function(function):
clean_user_groups()
@pytest.fixture(autouse=True)
def check_LDAP_db_integrity_call():
check_LDAP_db_integrity()
@ -42,6 +47,7 @@ def check_LDAP_db_integrity_call():
# List functions
#
def test_list_users():
res = user_list()['users']
@ -49,6 +55,7 @@ def test_list_users():
assert "bob" in res
assert "jack" in res
def test_list_groups():
res = user_group_list()['groups']
@ -65,7 +72,10 @@ def test_list_groups():
# Create - Remove functions
#
def test_create_user():
def test_create_user(mocker):
with message(mocker, "user_created"):
user_create("albert", "Albert", "Good", "alber@" + maindomain, "test123Ynh")
group_res = user_group_list()['groups']
@ -74,7 +84,10 @@ def test_create_user():
assert "albert" in group_res['albert']['members']
assert "albert" in group_res['all_users']['members']
def test_del_user():
def test_del_user(mocker):
with message(mocker, "user_deleted"):
user_delete("alice")
group_res = user_group_list()['groups']
@ -82,7 +95,10 @@ def test_del_user():
assert "alice" not in group_res
assert "alice" not in group_res['all_users']['members']
def test_create_group():
def test_create_group(mocker):
with message(mocker, "group_created", group="adminsys"):
user_group_create("adminsys")
group_res = user_group_list()['groups']
@ -90,7 +106,10 @@ def test_create_group():
assert "members" in group_res['adminsys'].keys()
assert group_res["adminsys"]["members"] == []
def test_del_group():
def test_del_group(mocker):
with message(mocker, "group_deleted", group="dev"):
user_group_delete("dev")
group_res = user_group_list()['groups']
@ -100,74 +119,93 @@ def test_del_group():
# Error on create / remove function
#
def test_create_user_with_mail_address_already_taken():
with pytest.raises(YunohostError):
def test_create_user_with_mail_address_already_taken(mocker):
with raiseYunohostError(mocker, "user_creation_failed"):
user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh")
def test_create_user_with_password_too_simple():
with pytest.raises(YunohostError):
def test_create_user_with_password_too_simple(mocker):
with raiseYunohostError(mocker, "password_listed"):
user_create("other", "Alice", "White", "other@" + maindomain, "12")
def test_create_user_already_exists():
with pytest.raises(YunohostError):
def test_create_user_already_exists(mocker):
with raiseYunohostError(mocker, "user_already_exists"):
user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh")
def test_update_user_with_mail_address_already_taken():
with pytest.raises(YunohostError):
def test_update_user_with_mail_address_already_taken(mocker):
with raiseYunohostError(mocker, "user_update_failed"):
user_update("bob", add_mailalias="alice@" + maindomain)
def test_del_user_that_does_not_exist():
with pytest.raises(YunohostError):
def test_del_user_that_does_not_exist(mocker):
with raiseYunohostError(mocker, "user_unknown"):
user_delete("doesnt_exist")
def test_create_group_all_users():
def test_create_group_all_users(mocker):
# Check groups already exist with special group "all_users"
with pytest.raises(YunohostError):
with raiseYunohostError(mocker, "group_already_exist"):
user_group_create("all_users")
def test_create_group_already_exists():
def test_create_group_already_exists(mocker):
# Check groups already exist (regular groups)
with pytest.raises(YunohostError):
with raiseYunohostError(mocker, "group_already_exist"):
user_group_create("dev")
def test_del_group_all_users():
with pytest.raises(YunohostError):
def test_del_group_all_users(mocker):
with raiseYunohostError(mocker, "group_cannot_be_deleted"):
user_group_delete("all_users")
def test_del_group_that_does_not_exist():
with pytest.raises(YunohostError):
def test_del_group_that_does_not_exist(mocker):
with raiseYunohostError(mocker, "group_unknown"):
user_group_delete("doesnt_exist")
#
# Update function
#
def test_update_user():
def test_update_user(mocker):
with message(mocker, "user_updated"):
user_update("alice", firstname="NewName", lastname="NewLast")
info = user_info("alice")
assert info['firstname'] == "NewName"
assert info['lastname'] == "NewLast"
def test_update_group_add_user():
def test_update_group_add_user(mocker):
with message(mocker, "group_updated", group="dev"):
user_group_update("dev", add=["bob"])
group_res = user_group_list()['groups']
assert set(group_res['dev']['members']) == set(["alice", "bob"])
def test_update_group_add_user_already_in():
def test_update_group_add_user_already_in(mocker):
with message(mocker, "group_user_already_in_group", user="bob", group="apps"):
user_group_update("apps", add=["bob"])
group_res = user_group_list()['groups']
assert group_res['apps']['members'] == ["bob"]
def test_update_group_remove_user():
def test_update_group_remove_user(mocker):
with message(mocker, "group_updated", group="apps"):
user_group_update("apps", remove=["bob"])
group_res = user_group_list()['groups']
assert group_res['apps']['members'] == []
def test_update_group_remove_user_not_already_in():
def test_update_group_remove_user_not_already_in(mocker):
with message(mocker, "group_user_not_in_group", user="jack", group="apps"):
user_group_update("apps", remove=["jack"])
group_res = user_group_list()['groups']
@ -177,29 +215,33 @@ def test_update_group_remove_user_not_already_in():
# Error on update functions
#
def test_update_user_that_doesnt_exist():
with pytest.raises(YunohostError):
def test_update_user_that_doesnt_exist(mocker):
with raiseYunohostError(mocker, "user_unknown"):
user_update("doesnt_exist", firstname="NewName", lastname="NewLast")
def test_update_group_that_doesnt_exist():
# Check groups not found
with pytest.raises(YunohostError):
def test_update_group_that_doesnt_exist(mocker):
with raiseYunohostError(mocker, "group_unknown"):
user_group_update("doesnt_exist", add=["alice"])
def test_update_group_all_users_manually():
with pytest.raises(YunohostError):
def test_update_group_all_users_manually(mocker):
with raiseYunohostError(mocker, "group_cannot_edit_all_users"):
user_group_update("all_users", remove=["alice"])
assert "alice" in user_group_list()["groups"]["all_users"]["members"]
def test_update_group_primary_manually():
with pytest.raises(YunohostError):
def test_update_group_primary_manually(mocker):
with raiseYunohostError(mocker, "group_cannot_edit_primary_group"):
user_group_update("alice", remove=["alice"])
assert "alice" in user_group_list()["groups"]["alice"]["members"]
def test_update_group_add_user_that_doesnt_exist():
# Check add bad user in group
with pytest.raises(YunohostError):
def test_update_group_add_user_that_doesnt_exist(mocker):
with raiseYunohostError(mocker, "user_unknown"):
user_group_update("dev", add=["doesnt_exist"])
assert "doesnt_exist" not in user_group_list()["groups"]["dev"]["members"]

View file

@ -350,25 +350,17 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
os.system('hostname yunohost.yunohost.org')
# Add a temporary SSOwat rule to redirect SSO to admin page
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise YunohostError('ssowat_persistent_conf_read_error', error=str(e))
except IOError:
if not os.path.exists('/etc/ssowat/conf.json.persistent'):
ssowat_conf = {}
else:
ssowat_conf = read_json('/etc/ssowat/conf.json.persistent')
if 'redirected_urls' not in ssowat_conf:
ssowat_conf['redirected_urls'] = {}
ssowat_conf['redirected_urls']['/'] = domain + '/yunohost/admin'
try:
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
except IOError as e:
raise YunohostError('ssowat_persistent_conf_write_error', error=str(e))
write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf)
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
# Create SSL CA
@ -616,8 +608,8 @@ def tools_upgrade(operation_logger, apps=None, system=False):
# randomly from yunohost itself... upgrading them is likely to
critical_packages = ("moulinette", "yunohost", "yunohost-admin", "ssowat", "python")
critical_packages_upgradable = [p for p in upgradables if p["name"] in critical_packages]
noncritical_packages_upgradable = [p for p in upgradables if p["name"] not in critical_packages]
critical_packages_upgradable = [p["name"] for p in upgradables if p["name"] in critical_packages]
noncritical_packages_upgradable = [p["name"] for p in upgradables if p["name"] not in critical_packages]
# Prepare dist-upgrade command
dist_upgrade = "DEBIAN_FRONTEND=noninteractive"

View file

@ -35,8 +35,10 @@ import subprocess
import copy
from moulinette import m18n
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml
from yunohost.utils.error import YunohostError
from yunohost.service import service_status
from yunohost.log import is_unit_operation
@ -195,21 +197,16 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
attr_dict['mail'] = [attr_dict['mail']] + aliases
# If exists, remove the redirection from the SSO
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise YunohostError('ssowat_persistent_conf_read_error', error=str(e))
except IOError:
if not os.path.exists('/etc/ssowat/conf.json.persistent'):
ssowat_conf = {}
else:
ssowat_conf = read_json('/etc/ssowat/conf.json.persistent')
if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']:
del ssowat_conf['redirected_urls']['/']
try:
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
except IOError as e:
raise YunohostError('ssowat_persistent_conf_write_error', error=str(e))
write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf)
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
try:
ldap.add('uid=%s,ou=users' % username, attr_dict)
@ -268,6 +265,11 @@ 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)
# 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()
@ -635,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)
@ -670,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"]

View file

@ -33,6 +33,8 @@ class YunohostError(MoulinetteError):
"""
def __init__(self, key, raw_msg=False, *args, **kwargs):
self.key = key # Saving the key is useful for unit testing
self.kwargs = kwargs # Saving the key is useful for unit testing
if raw_msg:
msg = key
else: