Merge pull request #795 from YunoHost/improve-permission-interface

Polish group/permissions interface + simplify code
This commit is contained in:
Alexandre Aubin 2019-10-04 20:02:35 +02:00 committed by GitHub
commit d063348d9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1159 additions and 1252 deletions

View file

@ -201,24 +201,34 @@ user:
subcategories: subcategories:
group: group:
subcategory_help: Manage group subcategory_help: Manage user groups
actions: actions:
### user_group_list() ### user_group_list()
list: list:
action_help: List group action_help: List existing groups
api: GET /users/groups api: GET /users/groups
arguments: arguments:
--fields: -s:
help: fields to fetch full: --short
nargs: "+" help: List only the names of groups
action: store_true
-f:
full: --full
help: Display all informations known about each groups
action: store_true
-p:
full: --include-primary-groups
help: Also display primary groups (each user has an eponym group that only contains itself)
action: store_true
default: false
### user_group_add() ### user_group_create()
add: create:
action_help: Create group action_help: Create group
api: POST /users/groups api: POST /users/groups
arguments: arguments:
groupname: groupname:
help: The unique group name to add help: Name of the group to be created
extra: extra:
pattern: &pattern_groupname pattern: &pattern_groupname
- !!str ^[a-z0-9_]+$ - !!str ^[a-z0-9_]+$
@ -230,7 +240,7 @@ user:
api: DELETE /users/groups/<groupname> api: DELETE /users/groups/<groupname>
arguments: arguments:
groupname: groupname:
help: Username to delete help: Name of the group to be deleted
extra: extra:
pattern: *pattern_groupname pattern: *pattern_groupname
@ -240,19 +250,19 @@ user:
api: PUT /users/groups/<groupname> api: PUT /users/groups/<groupname>
arguments: arguments:
groupname: groupname:
help: Username to update help: Name of the group to be updated
extra: extra:
pattern: *pattern_groupname pattern: *pattern_groupname
-a: -a:
full: --add-user full: --add
help: User to add in group help: User(s) to add in the group
nargs: "*" nargs: "*"
metavar: USERNAME metavar: USERNAME
extra: extra:
pattern: *pattern_username pattern: *pattern_username
-r: -r:
full: --remove-user full: --remove
help: User to remove in group help: User(s) to remove in the group
nargs: "*" nargs: "*"
metavar: USERNAME metavar: USERNAME
extra: extra:
@ -260,112 +270,62 @@ user:
### user_group_info() ### user_group_info()
info: info:
action_help: Get group information action_help: Get information about a specific group
api: GET /users/groups/<groupname> api: GET /users/groups/<groupname>
arguments: arguments:
groupname: groupname:
help: Groupname to get information help: Name of the group to fetch info about
extra: extra:
pattern: *pattern_username pattern: *pattern_username
permission: permission:
subcategory_help: Manage user permission subcategory_help: Manage permissions
actions: actions:
### user_permission_list() ### user_permission_list()
list: list:
action_help: List access to user and group action_help: List permissions and corresponding accesses
api: GET /users/permissions/<app> api: GET /users/permissions/<permission>
arguments: arguments:
-s:
full: --short
help: Only list permission names
action: store_true
-f:
full: --full
help: Display all info known about each permission, including the full user list of each group it is granted to.
action: store_true
### user_permission_update()
update:
action_help: Manage group or user permissions
api: POST /users/permissions/<permission>
arguments:
permission:
help: Permission to manage (e.g. mail or nextcloud or wordpress.editors)
-a: -a:
full: --app full: --add
help: Application to manage the permission help: Group or usernames to grant this permission to
nargs: "*" nargs: "*"
metavar: APP metavar: GROUP_OR_USER
-p:
full: --permission
help: Name of permission (main by default)
nargs: "*"
metavar: PERMISSION
-u:
full: --username
help: Username
nargs: "*"
metavar: USER
-g:
full: --group
help: Group name
nargs: "*"
metavar: GROUP
### user_permission_add()
add:
action_help: Grant access right to users and group
api: POST /users/permissions/<app>
arguments:
app:
help: Application to manage the permission
nargs: "+"
-p:
full: --permission
help: Name of permission (main by default)
nargs: "*"
metavar: PERMISSION
-u:
full: --username
help: Username
nargs: "*"
metavar: USER
extra: extra:
pattern: *pattern_username pattern: *pattern_username
-g: -r:
full: --group full: --remove
help: Group name help: Group or usernames revoke this permission from
nargs: "*" nargs: "*"
metavar: GROUP metavar: GROUP_OR_USER
extra: extra:
pattern: *pattern_username pattern: *pattern_username
### user_permission_remove() ## user_permission_reset()
remove: reset:
action_help: Revoke access right to users and group action_help: Reset allowed groups to the default (all_users) for a given permission
api: PUT /users/permissions/<app>
arguments:
app:
help: Application to manage the permission
nargs: "+"
-p:
full: --permission
help: Name of permission (main by default)
nargs: "*"
metavar: PERMISSION
-u:
full: --username
help: Username
nargs: "*"
metavar: USER
extra:
pattern: *pattern_username
-g:
full: --group
help: Group name
nargs: "*"
metavar: GROUP
extra:
pattern: *pattern_username
## user_permission_clear()
clear:
action_help: Reset access rights for the app
api: DELETE /users/permissions/<app> api: DELETE /users/permissions/<app>
arguments: arguments:
app: permission:
help: Application to manage the permission help: Permission to manage (e.g. mail or nextcloud or wordpress.editors)
nargs: "+"
-p:
full: --permission
help: Name of permission (main by default)
nargs: "*"
metavar: PERMISSION
ssh: ssh:
subcategory_help: Manage ssh access subcategory_help: Manage ssh access
@ -832,6 +792,7 @@ app:
addaccess: addaccess:
action_help: Grant access right to users (everyone by default) action_help: Grant access right to users (everyone by default)
api: PUT /access api: PUT /access
deprecated: true
arguments: arguments:
apps: apps:
nargs: "+" nargs: "+"
@ -843,6 +804,7 @@ app:
removeaccess: removeaccess:
action_help: Revoke access right to users (everyone by default) action_help: Revoke access right to users (everyone by default)
api: DELETE /access api: DELETE /access
deprecated: true
arguments: arguments:
apps: apps:
nargs: "+" nargs: "+"
@ -854,6 +816,7 @@ app:
clearaccess: clearaccess:
action_help: Reset access rights for the app action_help: Reset access rights for the app
api: POST /access api: POST /access
deprecated: true
arguments: arguments:
apps: apps:
nargs: "+" nargs: "+"

View file

@ -230,70 +230,82 @@ ynh_webpath_register () {
# Create a new permission for the app # Create a new permission for the app
# #
# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--urls "url" ["url" ...]] # usage: ynh_permission_create --permission "permission" [--urls "url" ["url" ...]]
# | arg: app - the application id
# | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: permission - the name for the permission (by default a permission named "main" already exist)
# | arg: defaultdisallow - define if all user will be allowed by default # | arg: urls - (optional) a list of FULL urls for the permission (e.g. domain.tld/apps/admin)
# | arg: urls - the list of urls for the the permission #
# example: ynh_permission_create --permission admin --urls domain.tld/blog/admin
ynh_permission_create() { ynh_permission_create() {
declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=urls= ) declare -Ar args_array=( [p]=permission= [u]=urls= )
local app
local permission local permission
local defaultdisallow
local urls local urls
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
if [[ -n ${defaultdisallow:-} ]]; then
defaultdisallow=",default_allow=False"
fi
if [[ -n ${urls:-} ]]; then if [[ -n ${urls:-} ]]; then
urls=",urls=['${urls//';'/"','"}']" urls=",urls=['${urls//';'/"','"}']"
fi fi
yunohost tools shell -c "from yunohost.permission import permission_add; permission_add('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission' ${urls:-}, sync_perm=False)"
} }
# Remove a permission for the app (note that when the app is removed all permission is automatically removed) # Remove a permission for the app (note that when the app is removed all permission is automatically removed)
# #
# usage: ynh_permission_remove --app "app" --permission "permission" # usage: ynh_permission_remove --permission "permission"
# | arg: app - the application id
# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
ynh_permission_remove() {
declare -Ar args_array=( [a]=app= [p]=permission= )
local app
local permission
ynh_handle_getopts_args "$@"
yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove('$app', '$permission', sync_perm=False)"
}
# Add a path managed by the SSO
# #
# usage: ynh_permission_add_path --app "app" --permission "permission" --url "url" ["url" ...] # example: ynh_permission_delete --permission editors
# | arg: app - the application id ynh_permission_delete() {
# | arg: permission - the name for the permission declare -Ar args_array=( [p]=permission= )
# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin)
ynh_permission_add_path() {
declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= )
local app
local permission local permission
local url
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)"
} }
# Remove a path managed by the SSO # Manage urls related to a permission
# #
# usage: ynh_permission_del_path --app "app" --permission "permission" --url "url" ["url" ...] # usage: ynh_permission_urls --permission "permission" --add "url" ["url" ...] --remove "url" ["url" ...]
# | arg: app - the application id # | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
# | arg: permission - the name for the permission # | arg: add - (optional) a list of FULL urls to add to the permission (e.g. domain.tld/apps/admin)
# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) # | arg: remove - (optional) a list of FULL urls to remove from the permission (e.g. other.tld/apps/admin)
ynh_permission_del_path() { #
declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) ynh_permission_urls() {
local app declare -Ar args_array=([p]=permission= [a]=add= [r]=remove=)
local permission local permission
local url local add
local remove
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" if [[ -n ${add:-} ]]; then
add=",add=['${add//';'/"','"}']"
fi
if [[ -n ${remove:-} ]]; then
remove=",remove=['${remove//';'/"','"}']"
fi
yunohost tools shell -c "from yunohost.permission import permission_urls; permission_urls('$app.$permission' ${add:-} ${remove:-})"
}
# Update a permission for the app
#
# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...]
# | arg: permission - the name for the permission (by default a permission named "main" already exist)
# | arg: add - the list of group or users to enable add to the permission
# | arg: remove - the list of group or users to remove from the permission
#
# example: ynh_permission_update --permission admin --add samdoe --remove all_users
ynh_permission_update() {
declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= )
local permission
local add
local remove
ynh_handle_getopts_args "$@"
if [[ -n ${add:-} ]]; then
add="--add ${add//';'/" "}"
fi
if [[ -n ${remove:-} ]]; then
remove="--remove ${remove//';'/" "} "
fi
yunohost user permission update "$app.$permission" ${add:-} ${remove:-}
} }

View file

@ -3,6 +3,5 @@ backup_dir="$1/conf/ynh/certs"
sudo mkdir -p /etc/yunohost/certs/ sudo mkdir -p /etc/yunohost/certs/
sudo cp -a $backup_dir/. /etc/yunohost/certs/ sudo cp -a $backup_dir/. /etc/yunohost/certs/
sudo yunohost app ssowatconf
sudo service nginx reload sudo service nginx reload
sudo service metronome reload sudo service metronome reload

View file

@ -59,16 +59,16 @@ children:
- groupOfNamesYnh - groupOfNamesYnh
depends_children: depends_children:
cn=main.mail,ou=permission: cn=mail.main,ou=permission:
cn: main.mail cn: mail.main
gidNumber: "5001" gidNumber: "5001"
objectClass: objectClass:
- posixGroup - posixGroup
- permissionYnh - permissionYnh
groupPermission: groupPermission:
- "cn=all_users,ou=groups,dc=yunohost,dc=org" - "cn=all_users,ou=groups,dc=yunohost,dc=org"
cn=main.metronome,ou=permission: cn=xmpp.main,ou=permission:
cn: main.metronome cn: xmpp.main
gidNumber: "5002" gidNumber: "5002"
objectClass: objectClass:
- posixGroup - posixGroup

View file

@ -3,7 +3,7 @@ auth_bind = yes
ldap_version = 3 ldap_version = 3
base = ou=users,dc=yunohost,dc=org base = ou=users,dc=yunohost,dc=org
user_attrs = uidNumber=500,gidNumber=8,mailuserquota=quota_rule=*:bytes=%$ user_attrs = uidNumber=500,gidNumber=8,mailuserquota=quota_rule=*:bytes=%$
user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org))
pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org))
default_pass_scheme = SSHA default_pass_scheme = SSHA

View file

@ -8,7 +8,7 @@ VirtualHost "{{ domain }}"
hostname = "localhost", hostname = "localhost",
user = { user = {
basedn = "ou=users,dc=yunohost,dc=org", basedn = "ou=users,dc=yunohost,dc=org",
filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.metronome,ou=permission,dc=yunohost,dc=org))", filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=xmpp.main,ou=permission,dc=yunohost,dc=org))",
usernamefield = "mail", usernamefield = "mail",
namefield = "cn", namefield = "cn",
}, },

View file

@ -1,5 +1,5 @@
server_host = localhost server_host = localhost
server_port = 389 server_port = 389
search_base = dc=yunohost,dc=org search_base = dc=yunohost,dc=org
query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org))
result_attribute = uid result_attribute = uid

View file

@ -1,5 +1,5 @@
server_host = localhost server_host = localhost
server_port = 389 server_port = 389
search_base = dc=yunohost,dc=org search_base = dc=yunohost,dc=org
query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org))
result_attribute = maildrop result_attribute = maildrop

View file

@ -51,10 +51,9 @@
"app_upgraded": "{app:s} upgraded", "app_upgraded": "{app:s} upgraded",
"apps_already_up_to_date": "All applications are already up-to-date", "apps_already_up_to_date": "All applications are already up-to-date",
"apps_permission_not_found": "No permission found for the installed apps", "apps_permission_not_found": "No permission found for the installed apps",
"apps_permission_restoration_failed": "Grant the permission permission '{permission:s}' to restore {app:s}",
"appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is damaged.", "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is damaged.",
"appslist_could_not_migrate": "Could not migrate the app list {appslist:s}! Could not parse the URL… The old cron job was kept kept in {bkp_file:s}.", "appslist_could_not_migrate": "Could not migrate the app list {appslist:s}! Could not parse the URL… The old cron job was kept kept in {bkp_file:s}.",
"appslist_fetched": "Updated application list {appslist:s} fetched", "appslist_fetched": "Updated application list {appslist:s}",
"appslist_migrating": "Migrating application list {appslist:s}…", "appslist_migrating": "Migrating application list {appslist:s}…",
"appslist_name_already_tracked": "A registered application list with name {name:s} already exists.", "appslist_name_already_tracked": "A registered application list with name {name:s} already exists.",
"appslist_removed": "{appslist:s} application list removed", "appslist_removed": "{appslist:s} application list removed",
@ -158,9 +157,9 @@
"domain_cannot_remove_main": "Cannot remove main domain. Set one first", "domain_cannot_remove_main": "Cannot remove main domain. Set one first",
"domain_cert_gen_failed": "Could not generate certificate", "domain_cert_gen_failed": "Could not generate certificate",
"domain_created": "Domain created", "domain_created": "Domain created",
"domain_creation_failed": "Could not create domain", "domain_creation_failed": "Could not create domain {domain}: {error}",
"domain_deleted": "Domain deleted", "domain_deleted": "Domain deleted",
"domain_deletion_failed": "Could not delete domain", "domain_deletion_failed": "Could not delete domain {domain}: {error}",
"domain_dns_conf_is_just_a_recommendation": "This command shows you the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.", "domain_dns_conf_is_just_a_recommendation": "This command shows you the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.",
"domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain", "domain_dyndns_already_subscribed": "You have already subscribed to a DynDNS domain",
"domain_dyndns_root_unknown": "Unknown DynDNS root domain", "domain_dyndns_root_unknown": "Unknown DynDNS root domain",
@ -186,9 +185,6 @@
"dyndns_registration_failed": "Could not register DynDNS domain: {error:s}", "dyndns_registration_failed": "Could not register DynDNS domain: {error:s}",
"dyndns_domain_not_provided": "DynDNS provider {provider:s} cannot provide domain {domain:s}.", "dyndns_domain_not_provided": "DynDNS provider {provider:s} cannot provide domain {domain:s}.",
"dyndns_unavailable": "The domain '{domain:s}' is unavailable.", "dyndns_unavailable": "The domain '{domain:s}' is unavailable.",
"edit_group_not_allowed": "You are not allowed to edit the group {group:s}",
"edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for the group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.",
"error_when_removing_sftpuser_group": "Could not remove the sftpusers group",
"executing_command": "Executing command '{command:s}'…", "executing_command": "Executing command '{command:s}'…",
"executing_script": "Executing script '{script:s}'…", "executing_script": "Executing script '{script:s}'…",
"extracting": "Extracting…", "extracting": "Extracting…",
@ -219,17 +215,19 @@
"global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is not a type supported by the system.", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is not a type supported by the system.",
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at-least 8 characters—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at-least 8 characters—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).",
"good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters—though it is good practice to use longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters—though it is good practice to use longer password (i.e. a passphrase) and/or to a variation of characters (uppercase, lowercase, digits and special characters).",
"group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' turned on for the app '{app:s}'", "group_already_exist": "Group {group} already exists",
"group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' turned off for the app '{app:s}'", "group_already_exist_on_system": "Group {group} already exists in the system groups",
"group_name_already_exist": "Group {name:s} already exists",
"group_created": "Group '{group}' created", "group_created": "Group '{group}' created",
"group_creation_failed": "Could not create the group '{group}'", "group_creation_failed": "Could not create the group '{group}': {error}",
"group_cannot_be_edited": "The group {group} cannot be edited manually.",
"group_cannot_be_deleted": "The group {group} cannot be deleted manually.",
"group_deleted": "Group '{group}' deleted", "group_deleted": "Group '{group}' deleted",
"group_deletion_failed": "Could not delete the group '{group}'", "group_deletion_failed": "Could not delete the group '{group}': {error}",
"group_deletion_not_allowed": "The group {group:s} cannot be deleted manually.",
"group_unknown": "The group '{group:s}' is unknown", "group_unknown": "The group '{group:s}' is unknown",
"group_updated": "Group '{group}' updated", "group_updated": "Group '{group}' updated",
"group_update_failed": "Could not update the group '{group}'", "group_update_failed": "Could not update the group '{group}': {error}",
"group_user_already_in_group": "User {user} is already in group {group}",
"group_user_not_in_group": "User {user} is not in group {group}",
"hook_exec_failed": "Could not run script: {path:s}", "hook_exec_failed": "Could not run script: {path:s}",
"hook_exec_not_terminated": "Script did not finish properly: {path:s}", "hook_exec_not_terminated": "Script did not finish properly: {path:s}",
"hook_json_return_error": "Could not read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}", "hook_json_return_error": "Could not read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}",
@ -247,9 +245,6 @@
"log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log display {name} --share' to get help", "log_help_to_get_failed_log": "The operation '{desc}' could not be completed. Please share the full log of this operation using the command 'yunohost log display {name} --share' to get help",
"log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list' to see all available operation logs", "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list' to see all available operation logs",
"log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly",
"log_app_addaccess": "Add access to '{}'",
"log_app_removeaccess": "Remove access to '{}'",
"log_app_clearaccess": "Remove all access to '{}'",
"log_app_fetchlist": "Add an application list", "log_app_fetchlist": "Add an application list",
"log_app_removelist": "Remove an application list", "log_app_removelist": "Remove an application list",
"log_app_change_url": "Change the URL of '{}' application", "log_app_change_url": "Change the URL of '{}' application",
@ -267,20 +262,20 @@
"log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'",
"log_dyndns_update": "Update the IP associated with your YunoHost subdomain '{}'", "log_dyndns_update": "Update the IP associated with your YunoHost subdomain '{}'",
"log_letsencrypt_cert_install": "Install a Let's encrypt certificate on '{}' domain", "log_letsencrypt_cert_install": "Install a Let's encrypt certificate on '{}' domain",
"log_permission_add": "Add the '{}' permission for the app '{}'", "log_permission_create": "Create permission '{}'",
"log_permission_remove": "Remove permission '{}'", "log_permission_delete": "Delete permission '{}'",
"log_permission_update": "Update permission '{}' for app '{}'", "log_permission_urls": "Update urls related to permission '{}'",
"log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain",
"log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate",
"log_regen_conf": "Regenerate system configurations '{}'", "log_regen_conf": "Regenerate system configurations '{}'",
"log_user_create": "Add '{}' user", "log_user_create": "Add '{}' user",
"log_user_delete": "Delete '{}' user", "log_user_delete": "Delete '{}' user",
"log_user_group_add": "Add '{}' group", "log_user_group_create": "Create '{}' group",
"log_user_group_delete": "Delete '{}' group", "log_user_group_delete": "Delete '{}' group",
"log_user_group_update": "Update '{}' group", "log_user_group_update": "Update '{}' group",
"log_user_update": "Update user info of '{}'", "log_user_update": "Update user info of '{}'",
"log_user_permission_add": "Update '{}' permission", "log_user_permission_update": "Update accesses for permission '{}'",
"log_user_permission_remove": "Update '{}' permission", "log_user_permission_reset": "Reset permission '{}'",
"log_tools_maindomain": "Make '{}' the main domain", "log_tools_maindomain": "Make '{}' the main domain",
"log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_migrations_migrate_forward": "Migrate forward",
"log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_postinstall": "Postinstall your YunoHost server",
@ -344,16 +339,17 @@
"migration_0008_no_warning": "No major risk indentified concerning overriding your SSH configuration—one can however not be absolutely sure ;)! Run the migration to override it. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk indentified concerning overriding your SSH configuration—one can however not be absolutely sure ;)! Run the migration to override it. Otherwise, you can also skip the migration - though it is not recommended.",
"migration_0009_not_needed": "This migration already happened somehow… (?) Skipping.", "migration_0009_not_needed": "This migration already happened somehow… (?) Skipping.",
"migration_0011_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", "migration_0011_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.",
"migration_0011_can_not_backup_before_migration": "Could not back up the system prior to migration. Error: {error:s}", "migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}",
"migration_0011_create_group": "Creating a group for each user…", "migration_0011_create_group": "Creating a group for each user…",
"migration_0011_done": "Migration successful. You are now able to manage usergroups.", "migration_0011_done": "Migration successful. You are now able to manage usergroups.",
"migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration needs to be updated.\nYou need to save your actual configuration, reintialize the original configuration by running 'yunohost tools regen-conf -f' and retry the migration", "migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration needs to be updated.\nYou need to save your current configuration, reintialize the original configuration by running 'yunohost tools regen-conf -f' and retry the migration",
"migration_0011_LDAP_update_failed": "Could not update LDAP. Error: {error:s}", "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_migrate_permission": "Migrating permissions from apps settings to LDAP…",
"migration_0011_migration_failed_trying_to_rollback": "Migration failed… trying to roll back the system.", "migration_0011_migration_failed_trying_to_rollback": "Migration failed… trying to roll back the system.",
"migration_0011_rollback_success": "System rolled back.", "migration_0011_rollback_success": "System rolled back.",
"migration_0011_update_LDAP_database": "Updating LDAP database…", "migration_0011_update_LDAP_database": "Updating LDAP database…",
"migration_0011_update_LDAP_schema": "Updating LDAP schema…", "migration_0011_update_LDAP_schema": "Updating LDAP schema…",
"migration_0011_failed_to_remove_stale_object": "Failed to remove stale object {dn}: {error}",
"migrations_already_ran": "Those migrations are already done: {ids}", "migrations_already_ran": "Those migrations are already done: {ids}",
"migrations_cant_reach_migration_file": "Could not access migrations files at path %s", "migrations_cant_reach_migration_file": "Could not access migrations files at path %s",
"migrations_dependencies_not_satisfied": "Cannot run migration {id} because first you need to run these migrations: {dependencies_id}", "migrations_dependencies_not_satisfied": "Cannot run migration {id} because first you need to run these migrations: {dependencies_id}",
@ -384,7 +380,6 @@
"mysql_db_creation_failed": "MySQL database creation failed", "mysql_db_creation_failed": "MySQL database creation failed",
"mysql_db_init_failed": "MySQL database init failed", "mysql_db_init_failed": "MySQL database init failed",
"mysql_db_initialized": "The MySQL database now initialized", "mysql_db_initialized": "The MySQL database now initialized",
"need_define_permission_before": "Redefine the permission using 'yunohost user permission add -u USER' before removing an allowed group",
"network_check_mx_ko": "DNS MX record is not set", "network_check_mx_ko": "DNS MX record is not set",
"network_check_smtp_ko": "Outbound e-mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ko": "Outbound e-mail (SMTP port 25) seems to be blocked by your network",
"network_check_smtp_ok": "Outbound e-mail (SMTP port 25) is not blocked", "network_check_smtp_ok": "Outbound e-mail (SMTP port 25) is not blocked",
@ -413,25 +408,23 @@
"pattern_positive_number": "Must be a positive number", "pattern_positive_number": "Must be a positive number",
"pattern_username": "Must be lower-case alphanumeric and underscore characters only", "pattern_username": "Must be lower-case alphanumeric and underscore characters only",
"pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}",
"permission_already_clear": "Permission '{permission:s}' already clear for app {app:s}", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled'",
"permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'",
"permission_created": "Permission '{permission:s}' for app {app:s} created", "permission_already_exist": "Permission '{permission}' already exists",
"permission_creation_failed": "Could not grant permission", "permission_cannot_remove_main": "Removing a main permission is not allowed",
"permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", "permission_created": "Permission '{permission:s}' created",
"permission_deletion_failed": "Missing permission '{permission:s}' to delete the app '{app:s}'", "permission_creation_failed": "Could not create permission '{permission}': {error}",
"permission_not_found": "Permission '{permission:s}' not found for the application '{app:s}'", "permission_deleted": "Permission '{permission:s}' deleted",
"permission_name_not_valid": "Pick an allowed permission name for '{permission:s}'", "permission_deletion_failed": "Could not delete permission '{permission}': {error}",
"permission_update_failed": "Could not update permission", "permission_not_found": "Permission '{permission:s}' not found",
"permission_generated": "Permission database updated", "permission_update_failed": "Could not update permission '{permission}' : {error}",
"permission_updated": "Permission '{permission:s}' for the app '{app:s}' updated", "permission_updated": "Permission '{permission:s}' updated",
"permission_update_nothing_to_do": "No permissions to update", "permission_update_nothing_to_do": "No permissions to update",
"port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections",
"port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections",
"port_available": "Port {port:d} is available", "port_available": "Port {port:d} is available",
"port_unavailable": "Port {port:d} is not available", "port_unavailable": "Port {port:d} is not available",
"recommend_to_add_first_user": "The post-install is finished, but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create <username>' or do it from the admin interface.", "recommend_to_add_first_user": "The post-install is finished, but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create <username>' or do it from the admin interface.",
"remove_main_permission_not_allowed": "Removing the main permission is not allowed",
"remove_user_of_group_not_allowed": "You are not allowed to remove the user '{user:s}' in the group '{group:s}'",
"regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'", "regenconf_file_backed_up": "Configuration file '{conf}' backed up to '{backup}'",
"regenconf_file_copy_failed": "Could not copy the new configuration file '{new}' to '{conf}'", "regenconf_file_copy_failed": "Could not copy the new configuration file '{new}' to '{conf}'",
"regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but was kept back.", "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but was kept back.",
@ -514,7 +507,6 @@
"ssowat_conf_updated": "SSOwat configuration updated", "ssowat_conf_updated": "SSOwat configuration updated",
"ssowat_persistent_conf_read_error": "Could not read persistent SSOwat configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "ssowat_persistent_conf_read_error": "Could not read persistent SSOwat configuration: {error:s}. Edit /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 /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 /etc/ssowat/conf.json.persistent file to fix the JSON syntax",
"system_groupname_exists": "Groupname already exists in the system group",
"system_upgraded": "System upgraded", "system_upgraded": "System upgraded",
"system_username_exists": "Username already exists in the list of system users", "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`.", "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`.",
@ -543,16 +535,15 @@
"upnp_disabled": "UPnP turned off", "upnp_disabled": "UPnP turned off",
"upnp_enabled": "UPnP turned on", "upnp_enabled": "UPnP turned on",
"upnp_port_open_failed": "Could not open port via UPnP", "upnp_port_open_failed": "Could not open port via UPnP",
"user_already_in_group": "The user '{user:}' is already in the '{group:s}' group", "user_already_exists": "User {user} already exists",
"user_created": "User created", "user_created": "User created",
"user_creation_failed": "Could not create user", "user_creation_failed": "Could not create user {user}: {error}",
"user_deleted": "User deleted", "user_deleted": "User deleted",
"user_deletion_failed": "Could not delete user", "user_deletion_failed": "Could not delete user {user}: {error}",
"user_home_creation_failed": "Could not create 'home' folder for user", "user_home_creation_failed": "Could not create 'home' folder for user",
"user_info_failed": "Could not retrieve user info", "user_info_failed": "Could not retrieve user info",
"user_not_in_group": "The user '{user:s}' is not in the group {group:s}",
"user_unknown": "Unknown user: {user:s}", "user_unknown": "Unknown user: {user:s}",
"user_update_failed": "Could not change user info", "user_update_failed": "Could not update user {user}: {error}",
"user_updated": "User info changed", "user_updated": "User info changed",
"users_available": "Available users:", "users_available": "Available users:",
"yunohost_already_installed": "YunoHost is already installed", "yunohost_already_installed": "YunoHost is already installed",

View file

@ -406,10 +406,10 @@ def app_map(app=None, raw=False, user=None):
""" """
from yunohost.permission import user_permission_list from yunohost.permission import user_permission_list
from yunohost.utils.ldap import _get_ldap_interface
apps = [] apps = []
result = {} result = {}
permissions = user_permission_list(full=True)["permissions"]
if app is not None: if app is not None:
if not _is_installed(app): if not _is_installed(app):
@ -429,11 +429,7 @@ def app_map(app=None, raw=False, user=None):
continue continue
if 'no_sso' in app_settings: # I don't think we need to check for the value here if 'no_sso' in app_settings: # I don't think we need to check for the value here
continue continue
if user is not None: if user and user not in permissions[app_id + ".main"]["corresponding_users"]:
ldap = _get_ldap_interface()
if not ldap.search(base='ou=permission,dc=yunohost,dc=org',
filter='(&(objectclass=permissionYnh)(cn=main.%s)(inheritPermission=uid=%s,ou=users,dc=yunohost,dc=org))' % (app_id, user),
attrs=['cn']):
continue continue
domain = app_settings['domain'] domain = app_settings['domain']
@ -465,7 +461,7 @@ def app_change_url(operation_logger, app, domain, path):
""" """
from yunohost.hook import hook_exec, hook_callback from yunohost.hook import hook_exec, hook_callback
from yunohost.domain import _normalize_domain_path, _get_conflicting_apps from yunohost.domain import _normalize_domain_path, _get_conflicting_apps
from yunohost.permission import permission_update from yunohost.permission import permission_urls
installed = _is_installed(app) installed = _is_installed(app)
if not installed: if not installed:
@ -555,7 +551,7 @@ def app_change_url(operation_logger, app, domain, path):
app_setting(app, 'domain', value=domain) app_setting(app, 'domain', value=domain)
app_setting(app, 'path', value=path) app_setting(app, 'path', value=path)
permission_update(app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path], sync_perm=True) permission_urls(app+".main", add=[domain+path], remove=[old_domain+old_path], sync_perm=True)
# avoid common mistakes # avoid common mistakes
if _run_service_command("reload", "nginx") is False: if _run_service_command("reload", "nginx") is False:
@ -765,11 +761,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
force -- Do not ask for confirmation when installing experimental / low-quality apps force -- Do not ask for confirmation when installing experimental / low-quality apps
""" """
from yunohost.utils.ldap import _get_ldap_interface
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
from yunohost.log import OperationLogger from yunohost.log import OperationLogger
from yunohost.permission import permission_add, permission_update, permission_remove, permission_sync_to_user from yunohost.permission import user_permission_list, permission_create, permission_urls, permission_delete, permission_sync_to_user
ldap = _get_ldap_interface()
# Fetch or extract sources # Fetch or extract sources
if not os.path.exists(INSTALL_TMP): if not os.path.exists(INSTALL_TMP):
@ -926,7 +920,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
# Create permission before the install (useful if the install script redefine the permission) # 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 # 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 # can't be sure that we don't have one case when it's needed
permission_add(app=app_instance_name, permission="main", sync_perm=False) permission_create(app_instance_name+".main", sync_perm=False)
# Execute the app install script # Execute the app install script
install_retcode = 1 install_retcode = 1
@ -970,11 +964,9 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
args=[app_instance_name], env=env_dict_remove args=[app_instance_name], env=env_dict_remove
)[0] )[0]
# Remove all permission in LDAP # Remove all permission in LDAP
result = ldap.search(base='ou=permission,dc=yunohost,dc=org', for permission_name in user_permission_list()["permissions"].keys():
filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) if permission_name.startswith(app_instance_name+"."):
permission_list = [p['cn'][0] for p in result] permission_delete(permission_name, force=True)
for l in permission_list:
permission_remove(app_instance_name, l.split('.')[0], force=True)
if remove_retcode != 0: if remove_retcode != 0:
msg = m18n.n('app_not_properly_removed', msg = m18n.n('app_not_properly_removed',
@ -1022,8 +1014,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
domain = app_settings.get('domain', None) domain = app_settings.get('domain', None)
path = app_settings.get('path', None) path = app_settings.get('path', None)
if domain and path: if domain and path:
permission_update(app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) permission_urls(app_instance_name+".main", add=[domain+path], sync_perm=False)
permission_sync_to_user() permission_sync_to_user()
logger.success(m18n.n('installation_complete')) logger.success(m18n.n('installation_complete'))
@ -1040,9 +1031,8 @@ def app_remove(operation_logger, app):
app -- App(s) to delete app -- App(s) to delete
""" """
from yunohost.utils.ldap import _get_ldap_interface
from yunohost.hook import hook_exec, hook_remove, hook_callback from yunohost.hook import hook_exec, hook_remove, hook_callback
from yunohost.permission import permission_remove, permission_sync_to_user from yunohost.permission import user_permission_list, permission_delete, permission_sync_to_user
if not _is_installed(app): if not _is_installed(app):
raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id()) raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id())
@ -1090,19 +1080,15 @@ def app_remove(operation_logger, app):
hook_remove(app) hook_remove(app)
# Remove all permission in LDAP # Remove all permission in LDAP
ldap = _get_ldap_interface() for permission_name in user_permission_list()["permissions"].keys():
result = ldap.search(base='ou=permission,dc=yunohost,dc=org', if permission_name.startswith(app+"."):
filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app, attrs=['cn']) permission_delete(permission_name, force=True, sync_perm=False)
permission_list = [p['cn'][0] for p in result]
for l in permission_list:
permission_remove(app, l.split('.')[0], force=True, sync_perm=False)
permission_sync_to_user() permission_sync_to_user()
_assert_system_is_sane_for_app(manifest, "post") _assert_system_is_sane_for_app(manifest, "post")
@is_unit_operation(['permission','app']) def app_addaccess(apps, users=[]):
def app_addaccess(operation_logger, apps, users=[]):
""" """
Grant access right to users (everyone by default) Grant access right to users (everyone by default)
@ -1113,15 +1099,15 @@ def app_addaccess(operation_logger, apps, users=[]):
""" """
from yunohost.permission import user_permission_update from yunohost.permission import user_permission_update
permission = user_permission_update(operation_logger, app=apps, permission="main", add_username=users) output = {}
for app in apps:
permission = user_permission_update(app+".main", add=users, remove="all_users")
output[app] = permission["corresponding_users"]
result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': output}
return {'allowed_users': result}
@is_unit_operation(['permission','app']) def app_removeaccess(apps, users=[]):
def app_removeaccess(operation_logger, apps, users=[]):
""" """
Revoke access right to users (everyone by default) Revoke access right to users (everyone by default)
@ -1132,15 +1118,15 @@ def app_removeaccess(operation_logger, apps, users=[]):
""" """
from yunohost.permission import user_permission_update from yunohost.permission import user_permission_update
permission = user_permission_update(operation_logger, app=apps, permission="main", del_username=users) output = {}
for app in apps:
permission = user_permission_update(app+".main", remove=users)
output[app] = permission["corresponding_users"]
result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': output}
return {'allowed_users': result}
@is_unit_operation(['permission','app']) def app_clearaccess(apps):
def app_clearaccess(operation_logger, apps):
""" """
Reset access rights for the app Reset access rights for the app
@ -1148,13 +1134,15 @@ def app_clearaccess(operation_logger, apps):
apps apps
""" """
from yunohost.permission import user_permission_clear from yunohost.permission import user_permission_reset
permission = user_permission_clear(operation_logger, app=apps, permission="main") output = {}
for app in apps:
permission = user_permission_reset(app+".main")
output[app] = permission["corresponding_users"]
result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': output}
return {'allowed_users': result}
def app_debug(app): def app_debug(app):
""" """
@ -1476,12 +1464,10 @@ def app_ssowatconf():
skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$") skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$")
skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$") skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$")
permission = {} permissions_per_url = {}
for a in user_permission_list()['permissions'].values(): for permission_name, permission_infos in user_permission_list(full=True)['permissions'].items():
for p in a.values(): for url in permission_infos["urls"]:
if 'URL' in p: permissions_per_url[url] = permission_infos['corresponding_users']
for u in p['URL']:
permission[u] = p['allowed_users']
conf_dict = { conf_dict = {
'portal_domain': main_domain, 'portal_domain': main_domain,
@ -1503,7 +1489,7 @@ def app_ssowatconf():
'redirected_regex': redirected_regex, 'redirected_regex': redirected_regex,
'users': {username: app_map(user=username) 'users': {username: app_map(user=username)
for username in user_list()['users'].keys()}, for username in user_list()['users'].keys()},
'permission': permission, 'permissions': permissions_per_url,
} }
with open('/etc/ssowat/conf.json', 'w+') as f: with open('/etc/ssowat/conf.json', 'w+') as f:

View file

@ -40,7 +40,7 @@ from moulinette import msignals, m18n
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
from moulinette.utils import filesystem from moulinette.utils import filesystem
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file, mkdir from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml
from yunohost.app import ( from yunohost.app import (
app_info, _is_installed, _parse_app_instance_name, _patch_php5 app_info, _is_installed, _parse_app_instance_name, _patch_php5
@ -677,6 +677,8 @@ class BackupManager():
backup_app_failed -- Raised at the end if the app backup script backup_app_failed -- Raised at the end if the app backup script
execution failed execution failed
""" """
from yunohost.permission import user_permission_list
app_setting_path = os.path.join('/etc/yunohost/apps/', app) app_setting_path = os.path.join('/etc/yunohost/apps/', app)
# Prepare environment # Prepare environment
@ -704,8 +706,9 @@ class BackupManager():
# backup permissions # backup permissions
logger.debug(m18n.n('backup_permission', app=app)) logger.debug(m18n.n('backup_permission', app=app))
ldap_url = "ldap:///dc=yunohost,dc=org???(&(objectClass=permissionYnh)(cn=*.%s))" % app permissions = user_permission_list(full=True)["permissions"]
os.system("slapcat -b dc=yunohost,dc=org -H '%s' -l '%s/permission.ldif'" % (ldap_url, settings_dir)) this_app_permissions = {name: infos for name, infos in permissions.items() if name.startswith(app + ".")}
write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions)
except: except:
abs_tmp_app_dir = os.path.join(self.work_dir, 'apps/', app) abs_tmp_app_dir = os.path.join(self.work_dir, 'apps/', app)
@ -919,7 +922,7 @@ class RestoreManager():
successfull_apps = self.targets.list("apps", include=["Success", "Warning"]) successfull_apps = self.targets.list("apps", include=["Success", "Warning"])
permission_sync_to_user(force=False) permission_sync_to_user()
if os.path.ismount(self.work_dir): if os.path.ismount(self.work_dir):
ret = subprocess.call(["umount", self.work_dir]) ret = subprocess.call(["umount", self.work_dir])
@ -1131,6 +1134,8 @@ class RestoreManager():
self._restore_system() self._restore_system()
self._restore_apps() self._restore_apps()
except Exception as e:
raise YunohostError("The following critical error happened during restoration: %s" % e)
finally: finally:
self.clean() self.clean()
@ -1183,18 +1188,12 @@ class RestoreManager():
if system_targets == []: if system_targets == []:
return return
from yunohost.utils.ldap import _get_ldap_interface from yunohost.user import user_group_list
ldap = _get_ldap_interface() from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list
# Backup old permission for apps # 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 # We need to do that because in case of an app is installed we can't remove the permission for this app
old_apps_permission = [] old_apps_permission = user_permission_list(ignore_system_perms=True, full=True)["permissions"]
try:
old_apps_permission = ldap.search('ou=permission,dc=yunohost,dc=org',
'(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))',
['cn', 'objectClass', 'groupPermission', 'URL', 'gidNumber'])
except:
logger.info(m18n.n('apps_permission_not_found'))
# Start register change on system # Start register change on system
operation_logger = OperationLogger('backup_restore_system') operation_logger = OperationLogger('backup_restore_system')
@ -1232,12 +1231,11 @@ class RestoreManager():
regen_conf() regen_conf()
# Check if we need to do the migration 0009 : setup group and permission # Check that at least a group exists (all_users) to know if we need to
# do the migration 0011 : setup group and permission
#
# Legacy code # Legacy code
result = ldap.search('ou=groups,dc=yunohost,dc=org', if not "all_users" in user_group_list()["groups"].keys():
'(&(objectclass=groupOfNamesYnh)(cn=all_users))',
['cn'])
if not result:
from yunohost.tools import _get_migration_by_name from yunohost.tools import _get_migration_by_name
setup_group_permission = _get_migration_by_name("setup_group_permission") setup_group_permission = _get_migration_by_name("setup_group_permission")
# Update LDAP schema restart slapd # Update LDAP schema restart slapd
@ -1245,25 +1243,16 @@ class RestoreManager():
regen_conf(names=['slapd'], force=True) regen_conf(names=['slapd'], force=True)
setup_group_permission.migrate_LDAP_db() setup_group_permission.migrate_LDAP_db()
# Remove all permission for all app which sill in the LDAP # Remove all permission for all app which is still in the LDAP
for per in ldap.search('ou=permission,dc=yunohost,dc=org', for permission_name in user_permission_list(ignore_system_perms=True)["permissions"].keys():
'(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))', permission_delete(permission_name, force=True)
['cn']):
if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]):
raise YunohostError('permission_deletion_failed',
permission=per['cn'][0].split('.')[0],
app=per['cn'][0].split('.')[1])
# Restore permission for the app which is installed # Restore permission for the app which is installed
for per in old_apps_permission: for permission_name, permission_infos in old_apps_permission.items():
try: app_name = permission_name.split(".")[0]
permission_name, app_name = per['cn'][0].split('.')
except:
logger.warning(m18n.n('permission_name_not_valid', permission=per['cn'][0]))
if _is_installed(app_name): if _is_installed(app_name):
if not ldap.add('cn=%s,ou=permission' % per['cn'][0], per): permission_create(permission_name, urls=permission_infos["urls"], sync_perm=False)
raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name) user_permission_update(permission_name, remove="all_users", add=permission_infos["allowed"])
def _restore_apps(self): def _restore_apps(self):
"""Restore all apps targeted""" """Restore all apps targeted"""
@ -1271,7 +1260,6 @@ class RestoreManager():
apps_targets = self.targets.list("apps", exclude=["Skipped"]) apps_targets = self.targets.list("apps", exclude=["Skipped"])
for app in apps_targets: for app in apps_targets:
print(app)
self._restore_app(app) self._restore_app(app)
def _restore_app(self, app_instance_name): def _restore_app(self, app_instance_name):
@ -1301,11 +1289,8 @@ class RestoreManager():
name already exists name already exists
restore_app_failed -- Raised if the restore bash script failed restore_app_failed -- Raised if the restore bash script failed
""" """
from moulinette.utils.filesystem import read_ldif
from yunohost.user import user_group_list from yunohost.user import user_group_list
from yunohost.permission import permission_remove from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
def copytree(src, dst, symlinks=False, ignore=None): def copytree(src, dst, symlinks=False, ignore=None):
for item in os.listdir(src): for item in os.listdir(src):
@ -1370,22 +1355,27 @@ class RestoreManager():
restore_script = os.path.join(tmp_folder_for_app_restore, 'restore') restore_script = os.path.join(tmp_folder_for_app_restore, 'restore')
# Restore permissions # Restore permissions
if os.path.isfile(app_settings_in_archive + '/permission.ldif'): if os.path.isfile('%s/permissions.yml' % app_settings_new_path):
filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass',
'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid'] permissions = read_yaml('%s/permissions.yml' % app_settings_new_path)
entries = read_ldif('%s/permission.ldif' % app_settings_in_archive, filtred_entries) existing_groups = user_group_list()['groups']
group_list = user_group_list(['cn'])['groups']
for dn, entry in entries: for permission_name, permission_infos in permissions.items():
# Remove the group which has been removed
for group in entry['groupPermission']: permission_create(permission_name, urls=permission_infos.get("urls", []))
group_name = group.split(',')[0].split('=')[1]
if group_name not in group_list: if "allowed" not in permission_infos:
entry['groupPermission'].remove(group) 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))
if not ldap.add('cn=%s,ou=permission' % entry['cn'][0], entry):
raise YunohostError('apps_permission_restoration_failed',
permission=entry['cn'][0].split('.')[0],
app=entry['cn'][0].split('.')[1])
else: 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)
os.remove('%s/permissions.yml' % app_settings_new_path)
else:
# Otherwise, we need to migrate the legacy permissions of this
# app (included in its settings.yml)
from yunohost.tools import _get_migration_by_name from yunohost.tools import _get_migration_by_name
setup_group_permission = _get_migration_by_name("setup_group_permission") setup_group_permission = _get_migration_by_name("setup_group_permission")
setup_group_permission.migrate_app_permission(app=app_instance_name) setup_group_permission.migrate_app_permission(app=app_instance_name)
@ -1424,7 +1414,6 @@ class RestoreManager():
operation_logger.start() operation_logger.start()
# Execute remove script # Execute remove script
# TODO: call app_remove instead
if hook_exec(remove_script, args=[app_instance_name], if hook_exec(remove_script, args=[app_instance_name],
env=env_dict_remove)[0] != 0: env=env_dict_remove)[0] != 0:
msg = m18n.n('app_not_properly_removed', app=app_instance_name) msg = m18n.n('app_not_properly_removed', app=app_instance_name)
@ -1436,12 +1425,10 @@ class RestoreManager():
# Cleaning app directory # Cleaning app directory
shutil.rmtree(app_settings_new_path, ignore_errors=True) shutil.rmtree(app_settings_new_path, ignore_errors=True)
# Remove all permission in LDAP # Remove all permission in LDAP for this app
result = ldap.search(base='ou=permission,dc=yunohost,dc=org', for permission_name in user_permission_list()["permissions"].keys():
filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) if permission_name.startswith(app_instance_name+"."):
permission_list = [p['cn'][0] for p in result] permission_delete(permission_name, force=True)
for l in permission_list:
permission_remove(app_instance_name, l.split('.')[0], force=True)
# TODO Cleaning app hooks # TODO Cleaning app hooks
else: else:

View file

@ -1,17 +1,16 @@
import yaml
import time import time
import os import os
from moulinette import m18n from moulinette import m18n
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_yaml
from yunohost.tools import Migration from yunohost.tools import Migration
from yunohost.user import user_group_add, user_group_update from yunohost.user import user_group_create, user_group_update
from yunohost.app import app_setting, app_list from yunohost.app import app_setting, app_list
from yunohost.regenconf import regen_conf from yunohost.regenconf import regen_conf
from yunohost.permission import permission_add, permission_sync_to_user from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user
from yunohost.user import user_permission_add
logger = getActionLogger('yunohost.migration') logger = getActionLogger('yunohost.migration')
@ -19,6 +18,7 @@ logger = getActionLogger('yunohost.migration')
# Tools used also for restoration # Tools used also for restoration
################################################### ###################################################
class MyMigration(Migration): class MyMigration(Migration):
""" """
Update the LDAP DB to be able to store the permission Update the LDAP DB to be able to store the permission
@ -28,6 +28,28 @@ class MyMigration(Migration):
required = True required = True
def remove_if_exists(self, target):
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
try:
objects = ldap.search(target + ",dc=yunohost,dc=org")
# ldap search will raise an exception if no corresponding object is found >.> ...
except Exception as e:
logger.debug("%s does not exist, no need to delete it" % target)
return
objects.reverse()
for o in objects:
for dn in o["dn"]:
dn = dn.replace(",dc=yunohost,dc=org", "")
logger.debug("Deleting old object %s ..." % dn)
try:
ldap.remove(dn)
except Exception as e:
raise YunohostError("migration_0011_failed_to_remove_stale_object", dn=dn, error=e)
def migrate_LDAP_db(self): def migrate_LDAP_db(self):
logger.info(m18n.n("migration_0011_update_LDAP_database")) logger.info(m18n.n("migration_0011_update_LDAP_database"))
@ -35,15 +57,13 @@ class MyMigration(Migration):
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
try: ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml')
ldap.remove('cn=sftpusers,ou=groups')
except:
logger.warn(m18n.n("error_when_removing_sftpuser_group"))
with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f:
ldap_map = yaml.load(f)
try: try:
self.remove_if_exists("cn=sftpusers,ou=groups")
self.remove_if_exists("ou=permission")
self.remove_if_exists('cn=all_users,ou=groups')
attr_dict = ldap_map['parents']['ou=permission'] attr_dict = ldap_map['parents']['ou=permission']
ldap.add('ou=permission', attr_dict) ldap.add('ou=permission', attr_dict)
@ -65,10 +85,8 @@ class MyMigration(Migration):
username = user_info['uid'][0] username = user_info['uid'][0]
ldap.update('uid=%s,ou=users' % username, ldap.update('uid=%s,ou=users' % username,
{'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']})
user_group_add(username, gid=user_info['uidNumber'][0], sync_perm=False) user_group_create(username, gid=user_info['uidNumber'][0], primary_group=True, sync_perm=False)
user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) user_group_update(groupname='all_users', add=username, force=True, sync_perm=False)
user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=False)
def migrate_app_permission(self, app=None): def migrate_app_permission(self, app=None):
logger.info(m18n.n("migration_0011_migrate_permission")) logger.info(m18n.n("migration_0011_migrate_permission"))
@ -85,14 +103,18 @@ class MyMigration(Migration):
domain = app_setting(app, 'domain') domain = app_setting(app, 'domain')
urls = [domain + path] if domain and path else None urls = [domain + path] if domain and path else None
permission_add(app, permission='main', urls=urls, default_allow=True, sync_perm=False) permission_create(app+".main", urls=urls, sync_perm=False)
if permission: if permission:
allowed_group = permission.split(',') allowed_group = permission.split(',')
user_permission_add([app], permission='main', group=allowed_group, sync_perm=False) user_permission_update(app+".main", remove="all_users", add=allowed_group, sync_perm=False)
app_setting(app, 'allowed_users', delete=True) app_setting(app, 'allowed_users', delete=True)
def run(self): def run(self):
# FIXME : what do we really want to do here ...
# Imho we should just force-regen the conf in all case, and maybe
# just display a warning if we detect that the conf was manually modified
# Check if the migration can be processed # Check if the migration can be processed
ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True) ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True)
# By this we check if the have been customized # By this we check if the have been customized

View file

@ -112,8 +112,10 @@ def domain_add(operation_logger, domain, dyndns=False):
'virtualdomain': domain, 'virtualdomain': domain,
} }
if not ldap.add('virtualdomain=%s,ou=domains' % domain, attr_dict): try:
raise YunohostError('domain_creation_failed') ldap.add('virtualdomain=%s,ou=domains' % domain, attr_dict)
except Exception as e:
raise YunohostError('domain_creation_failed', domain=domain, error=e)
# Don't regen these conf if we're still in postinstall # Don't regen these conf if we're still in postinstall
if os.path.exists('/etc/yunohost/installed'): if os.path.exists('/etc/yunohost/installed'):
@ -167,10 +169,12 @@ def domain_remove(operation_logger, domain, force=False):
operation_logger.start() operation_logger.start()
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
if ldap.remove('virtualdomain=' + domain + ',ou=domains') or force: try:
ldap.remove('virtualdomain=' + domain + ',ou=domains')
except Exception as e:
raise YunohostError('domain_deletion_failed', domain=domain, error=e)
os.system('rm -rf /etc/yunohost/certs/%s' % domain) os.system('rm -rf /etc/yunohost/certs/%s' % domain)
else:
raise YunohostError('domain_deletion_failed')
regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix'])
app_ssowatconf() app_ssowatconf()

View file

@ -44,7 +44,7 @@ CATEGORIES = ['operation', 'history', 'package', 'system', 'access', 'service',
'app'] 'app']
METADATA_FILE_EXT = '.yml' METADATA_FILE_EXT = '.yml'
LOG_FILE_EXT = '.log' LOG_FILE_EXT = '.log'
RELATED_CATEGORIES = ['app', 'domain', 'service', 'user'] RELATED_CATEGORIES = ['app', 'domain', 'group', 'service', 'user']
logger = getActionLogger('yunohost.log') logger = getActionLogger('yunohost.log')
@ -213,7 +213,7 @@ def log_display(path, number=None, share=False):
return infos return infos
def is_unit_operation(entities=['app', 'domain', 'service', 'user'], def is_unit_operation(entities=['app', 'domain', 'group', 'service', 'user'],
exclude=['password'], operation_key=None): exclude=['password'], operation_key=None):
""" """
Configure quickly a unit operation Configure quickly a unit operation

View file

@ -24,6 +24,7 @@
Manage permissions Manage permissions
""" """
import copy
import grp import grp
import random import random
@ -35,309 +36,247 @@ from yunohost.log import is_unit_operation
logger = getActionLogger('yunohost.user') logger = getActionLogger('yunohost.user')
SYSTEM_PERMS = ["mail", "xmpp", "stfp"]
def user_permission_list(app=None, permission=None, username=None, group=None): #
#
# The followings are the methods exposed through the "yunohost user permission" interface
#
#
def user_permission_list(short=False, full=False, ignore_system_perms=False):
""" """
List permission for specific application List permissions and corresponding accesses
Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail
permission -- name of the permission ("main" by default)
username -- Username to get informations
group -- Groupname to get informations
""" """
from yunohost.utils.ldap import _get_ldap_interface # Fetch relevant informations
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org',
'(objectclass=permissionYnh)',
["cn", 'groupPermission', 'inheritPermission', 'URL'])
permission_attrs = [ # Parse / organize information to be outputed
'cn',
'groupPermission',
'inheritPermission',
'URL',
]
# Normally app is alway defined but it should be possible to set it
if app and not isinstance(app, list):
app = [app]
if permission and not isinstance(permission, list):
permission = [permission]
if not isinstance(username, list):
username = [username]
if not isinstance(group, list):
group = [group]
permissions = {} permissions = {}
for infos in permissions_infos:
result = ldap.search('ou=permission,dc=yunohost,dc=org', name = infos['cn'][0]
'(objectclass=permissionYnh)', permission_attrs)
for res in result: if ignore_system_perms and name.split(".")[0] in SYSTEM_PERMS:
try:
permission_name, app_name = res['cn'][0].split('.')
except:
logger.warning(m18n.n('permission_name_not_valid', permission=res['cn'][0]))
group_name = []
if 'groupPermission' in res:
for g in res['groupPermission']:
group_name.append(g.split("=")[1].split(",")[0])
user_name = []
if 'inheritPermission' in res:
for u in res['inheritPermission']:
user_name.append(u.split("=")[1].split(",")[0])
# Don't show the result if the user defined a specific permission, user or group
if app and app_name not in app:
continue
if permission and permission_name not in permission:
continue
if username[0] and not set(username) & set(user_name):
continue
if group[0] and not set(group) & set(group_name):
continue continue
if app_name not in permissions: permissions[name] = {}
permissions[app_name] = {} permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])]
permissions[app_name][permission_name] = {'allowed_users': [], 'allowed_groups': []} if full:
for g in group_name: permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])]
permissions[app_name][permission_name]['allowed_groups'].append(g) permissions[name]["urls"] = infos.get("URL", [])
for u in user_name:
permissions[app_name][permission_name]['allowed_users'].append(u) if short:
if 'URL' in res: permissions = permissions.keys()
permissions[app_name][permission_name]['URL'] = []
for u in res['URL']:
permissions[app_name][permission_name]['URL'].append(u)
return {'permissions': permissions} return {'permissions': permissions}
def user_permission_update(operation_logger, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None, sync_perm=True): @is_unit_operation()
def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True):
""" """
Allow or Disallow a user or group to a permission for a specific application Allow or Disallow a user or group to a permission for a specific application
Keyword argument: Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail permission -- Name of the permission (e.g. mail or or wordpress or wordpress.editors)
permission -- name of the permission ("main" by default) add -- List of groups or usernames to add to this permission
add_username -- Username to allow remove -- List of groups or usernames to remove from to this permission
add_group -- Groupname to allow
del_username -- Username to disallow
del_group -- Groupname to disallow
""" """
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
from yunohost.user import user_group_list from yunohost.user import user_group_list
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
if permission: # By default, manipulate main permission
if not isinstance(permission, list): if "." not in permission:
permission = [permission] permission = permission + ".main"
# Fetch currently allowed groups for this permission
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
if existing_permission is None:
raise YunohostError('permission_not_found', permission=permission)
current_allowed_groups = existing_permission["allowed"]
all_existing_groups = user_group_list()['groups'].keys()
operation_logger.related_to.append(('app', permission.split(".")[0]))
# Compute new allowed group list (and make sure what we're doing make sense)
new_allowed_groups = copy.copy(current_allowed_groups)
if add:
groups_to_add = [add] if not isinstance(add, list) else add
for group in groups_to_add:
if group not in all_existing_groups:
raise YunohostError('group_unknown', group=group)
if group in current_allowed_groups:
logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group))
else: else:
permission = ["main"] operation_logger.related_to.append(('group', group))
if add_group: new_allowed_groups += groups_to_add
if not isinstance(add_group, list):
add_group = [add_group] if remove:
groups_to_remove = [remove] if not isinstance(remove, list) else remove
for group in groups_to_remove:
if group not in all_existing_groups:
raise YunohostError('group_unknown', group=group)
if group not in current_allowed_groups:
logger.warning(m18n.n('permission_already_disallowed', permission=permission, group=group))
else: else:
add_group = [] operation_logger.related_to.append(('group', group))
if add_username: new_allowed_groups = [g for g in new_allowed_groups if g not in groups_to_remove]
if not isinstance(add_username, list):
add_username = [add_username]
else:
add_username = []
if del_group: # If we end up with something like allowed groups is ["all_users", "volunteers"]
if not isinstance(del_group, list): # we shall warn the users that they should probably choose between one or the other,
del_group = [del_group] # because the current situation is probably not what they expect / is temporary ?
else:
del_group = []
if del_username: if len(new_allowed_groups) > 1 and "all_users" in new_allowed_groups:
if not isinstance(del_username, list): # FIXME : i18n
del_username = [del_username] # FIXME : write a better explanation ?
else: 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.")
del_username = []
# Validate that the group exist # Don't update LDAP if we update exactly the same values
for g in add_group: if set(new_allowed_groups) == set(current_allowed_groups):
if g not in user_group_list(['cn'])['groups']: # FIXME : i18n
raise YunohostError('group_unknown', group=g) logger.warning("The permission was not updated all addition/removal requests already match the current state.")
for u in add_username: return
if u not in user_list(['uid'])['users']:
raise YunohostError('user_unknown', user=u)
for g in del_group:
if g not in user_group_list(['cn'])['groups']:
raise YunohostError('group_unknown', group=g)
for u in del_username:
if u not in user_list(['uid'])['users']:
raise YunohostError('user_unknown', user=u)
# Merge user and group (note that we consider all user as a group) # Commit the new allowed group list
add_group.extend(add_username)
del_group.extend(del_username)
if 'all_users' in add_group or 'all_users' in del_group:
raise YunohostError('edit_permission_with_group_all_users_not_allowed')
# Populate permission informations
permission_attrs = [
'cn',
'groupPermission',
]
result = ldap.search('ou=permission,dc=yunohost,dc=org',
'(objectclass=permissionYnh)', permission_attrs)
result = {p['cn'][0]: p for p in result}
new_per_dict = {}
for a in app:
for per in permission:
permission_name = per + '.' + a
if permission_name not in result:
raise YunohostError('permission_not_found', permission=per, app=a)
new_per_dict[permission_name] = set()
if 'groupPermission' in result[permission_name]:
new_per_dict[permission_name] = set(result[permission_name]['groupPermission'])
for g in del_group:
if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]:
raise YunohostError('need_define_permission_before')
group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org'
if group_name not in new_per_dict[permission_name]:
logger.warning(m18n.n('group_already_disallowed', permission=per, app=a, group=g))
else:
new_per_dict[permission_name].remove(group_name)
if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]:
new_per_dict[permission_name].remove('cn=all_users,ou=groups,dc=yunohost,dc=org')
for g in add_group:
group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org'
if group_name in new_per_dict[permission_name]:
logger.warning(m18n.n('group_already_allowed', permission=per, app=a, group=g))
else:
new_per_dict[permission_name].add(group_name)
operation_logger.start() operation_logger.start()
for per, val in new_per_dict.items(): try:
# Don't update LDAP if we update exactly the same values ldap.update('cn=%s,ou=permission' % permission,
if val == set(result[per]['groupPermission'] if 'groupPermission' in result[per] else []): {'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]})
continue except Exception as e:
if ldap.update('cn=%s,ou=permission' % per, {'groupPermission': val}): raise YunohostError('permission_update_failed', permission=permission, error=e)
p = per.split('.')
logger.debug(m18n.n('permission_updated', permission=p[0], app=p[1])) logger.debug(m18n.n('permission_updated', permission=permission))
else:
raise YunohostError('permission_update_failed') # Trigger permission sync if asked
if sync_perm: if sync_perm:
permission_sync_to_user() permission_sync_to_user()
for a in app: new_permission = user_permission_list(full=True)["permissions"][permission]
allowed_users = set()
disallowed_users = set()
group_list = user_group_list(['member'])['groups']
for g in add_group: # Trigger app callbacks
if 'members' in group_list[g]:
allowed_users.union(group_list[g]['members'])
for g in del_group:
if 'members' in group_list[g]:
disallowed_users.union(group_list[g]['members'])
allowed_users = ','.join(allowed_users) app = permission.split(".")[0]
disallowed_users = ','.join(disallowed_users)
if add_group:
hook_callback('post_app_addaccess', args=[app, allowed_users])
if del_group:
hook_callback('post_app_removeaccess', args=[app, disallowed_users])
return user_permission_list(app, permission) old_allowed_users = set(existing_permission["corresponding_users"])
new_allowed_users = set(new_permission["corresponding_users"])
effectively_added_users = new_allowed_users - old_allowed_users
effectively_removed_users = old_allowed_users - new_allowed_users
if effectively_added_users:
hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)])
if effectively_removed_users:
hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)])
return new_permission
def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True): @is_unit_operation()
def user_permission_reset(operation_logger, permission, sync_perm=True):
""" """
Reset the permission for a specific application Reset a given permission to just 'all_users'
Keyword argument: Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
permission -- name of the permission ("main" by default)
username -- Username to get informations (all by default)
group -- Groupname to get informations (all by default)
""" """
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
if permission: # By default, manipulate main permission
if not isinstance(permission, list): if "." not in permission:
permission = [permission] permission = permission + ".main"
else:
permission = ["main"] # Fetch existing permission
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
if existing_permission is None:
raise YunohostError('permission_not_found', permission=permission)
# Update permission with default (all_users)
operation_logger.related_to.append(('app', permission.split(".")[0]))
operation_logger.start()
default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']}
try:
ldap.update('cn=%s,ou=permission' % permission, default_permission)
except Exception as e:
raise YunohostError('permission_update_failed', permission=permission, error=e)
# Populate permission informations logger.debug(m18n.n('permission_updated', permission=permission))
permission_attrs = [
'cn',
'groupPermission',
]
result = ldap.search('ou=permission,dc=yunohost,dc=org',
'(objectclass=permissionYnh)', permission_attrs)
result = {p['cn'][0]: p for p in result}
for a in app:
for per in permission:
permission_name = per + '.' + a
if permission_name not in result:
raise YunohostError('permission_not_found', permission=per, app=a)
if 'groupPermission' in result[permission_name] and 'cn=all_users,ou=groups,dc=yunohost,dc=org' in result[permission_name]['groupPermission']:
logger.warning(m18n.n('permission_already_clear', permission=per, app=a))
continue
if ldap.update('cn=%s,ou=permission' % permission_name, default_permission):
logger.debug(m18n.n('permission_updated', permission=per, app=a))
else:
raise YunohostError('permission_update_failed')
if sync_perm:
permission_sync_to_user() permission_sync_to_user()
for a in app: new_permission = user_permission_list(full=True)["permissions"][permission]
permission_name = 'main.' + a
result = ldap.search('ou=permission,dc=yunohost,dc=org',
filter='cn=' + permission_name, attrs=['inheritPermission'])
if result:
allowed_users = result[0]['inheritPermission']
new_user_list = ','.join(allowed_users)
hook_callback('post_app_removeaccess', args=[app, new_user_list])
return user_permission_list(app, permission) # Trigger app callbacks
app = permission.split(".")[0]
old_allowed_users = set(existing_permission["corresponding_users"])
new_allowed_users = set(new_permission["corresponding_users"])
effectively_added_users = new_allowed_users - old_allowed_users
effectively_removed_users = old_allowed_users - new_allowed_users
if effectively_added_users:
hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)])
if effectively_removed_users:
hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)])
return new_permission
#
#
# The followings methods are *not* directly exposed.
# They are used to create/delete the permissions (e.g. during app install/remove)
# and by some app helpers to possibly add additional permissions and tweak the urls
#
#
@is_unit_operation(['permission', 'app']) @is_unit_operation()
def permission_add(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True): def permission_create(operation_logger, permission, urls=None, sync_perm=True):
""" """
Create a new permission for a specific application Create a new permission for a specific application
Keyword argument: Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
permission -- name of the permission ("main" by default)
urls -- list of urls to specify for the permission urls -- list of urls to specify for the permission
""" """
from yunohost.domain import _normalize_domain_path
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
# By default, manipulate main permission
if "." not in permission:
permission = permission + ".main"
# Validate uniqueness of permission in LDAP # Validate uniqueness of permission in LDAP
permission_name = str(permission + '.' + app) # str(...) Fix encoding issue if ldap.get_conflict({'cn': permission},
conflict = ldap.get_conflict({ base_dn='ou=permission,dc=yunohost,dc=org'):
'cn': permission_name raise YunohostError('permission_already_exist', permission=permission)
}, base_dn='ou=permission,dc=yunohost,dc=org')
if conflict:
raise YunohostError('permission_already_exist', permission=permission, app=app)
# Get random GID # Get random GID
all_gid = {x.gr_gid for x in grp.getgrall()} all_gid = {x.gr_gid for x in grp.getgrall()}
@ -349,180 +288,175 @@ def permission_add(operation_logger, app, permission, urls=None, default_allow=T
attr_dict = { attr_dict = {
'objectClass': ['top', 'permissionYnh', 'posixGroup'], 'objectClass': ['top', 'permissionYnh', 'posixGroup'],
'cn': permission_name, 'cn': str(permission),
'gidNumber': gid, 'gidNumber': gid,
} }
if default_allow:
attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' # For main permission, we add all users by default
if permission.endswith(".main"):
attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org']
if urls: if urls:
attr_dict['URL'] = [] attr_dict['URL'] = [_normalize_url(url) for url in urls]
for url in urls:
domain = url[:url.index('/')]
path = url[url.index('/'):]
domain, path = _normalize_domain_path(domain, path)
attr_dict['URL'].append(domain + path)
operation_logger.related_to.append(('app', permission.split(".")[0]))
operation_logger.start() operation_logger.start()
if ldap.add('cn=%s,ou=permission' % permission_name, attr_dict):
try:
ldap.add('cn=%s,ou=permission' % permission, attr_dict)
except Exception as e:
raise YunohostError('permission_creation_failed', permission=permission, error=e)
if sync_perm: if sync_perm:
permission_sync_to_user() permission_sync_to_user()
logger.debug(m18n.n('permission_created', permission=permission, app=app))
return user_permission_list(app, permission)
raise YunohostError('permission_creation_failed') logger.debug(m18n.n('permission_created', permission=permission))
return user_permission_list(full=True)["permissions"][permission]
@is_unit_operation(['permission', 'app']) @is_unit_operation()
def permission_update(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True): def permission_urls(operation_logger, permission, add=None, remove=None, sync_perm=True):
""" """
Update a permission for a specific application Update urls related to a permission for a specific application
Keyword argument: Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
permission -- name of the permission ("main" by default) add -- List of urls to add
add_url -- Add a new url for a permission remove -- List of urls to remove
remove_url -- Remove a url for a permission
""" """
from yunohost.domain import _normalize_domain_path
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
permission_name = str(permission + '.' + app) # str(...) Fix encoding issue # Fetch existing permission
# Populate permission informations existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
result = ldap.search(base='ou=permission,dc=yunohost,dc=org', if not existing_permission:
filter='cn=' + permission_name, attrs=['URL']) raise YunohostError('permission_not_found', permission=permission)
if not result:
raise YunohostError('permission_not_found', permission=permission, app=app)
permission_obj = result[0]
if 'URL' not in permission_obj: # Compute new url list
permission_obj['URL'] = []
url = set(permission_obj['URL']) new_urls = copy.copy(existing_permission["urls"])
if add_url: if add:
for u in add_url: urls_to_add = [add] if not isinstance(add, list) else add
domain = u[:u.index('/')] urls_to_add = [_normalize_url(url) for url in urls_to_add]
path = u[u.index('/'):] new_urls += urls_to_add
domain, path = _normalize_domain_path(domain, path) if remove:
url.add(domain + path) urls_to_remove = [remove] if not isinstance(remove, list) else remove
if remove_url: urls_to_remove = [_normalize_url(url) for url in urls_to_remove]
for u in remove_url: new_urls = [u for u in new_urls if u not in urls_to_remove]
domain = u[:u.index('/')]
path = u[u.index('/'):]
domain, path = _normalize_domain_path(domain, path)
url.discard(domain + path)
if url == set(permission_obj['URL']): if set(new_urls) == set(existing_permission["urls"]):
logger.warning(m18n.n('permission_update_nothing_to_do')) logger.warning(m18n.n('permission_update_nothing_to_do'))
return user_permission_list(app, permission) return existing_permission
# Actually commit the change
operation_logger.related_to.append(('app', permission.split(".")[0]))
operation_logger.start() operation_logger.start()
if ldap.update('cn=%s,ou=permission' % permission_name, {'cn': permission_name, 'URL': url}):
try:
ldap.update('cn=%s,ou=permission' % permission, {'URL': new_urls})
except Exception as e:
raise YunohostError('permission_update_failed', permission=permission, error=e)
if sync_perm: if sync_perm:
permission_sync_to_user() permission_sync_to_user()
logger.debug(m18n.n('permission_updated', permission=permission, app=app))
return user_permission_list(app, permission)
raise YunohostError('premission_update_failed') logger.debug(m18n.n('permission_updated', permission=permission))
return user_permission_list(full=True)["permissions"][permission]
@is_unit_operation(['permission', 'app']) @is_unit_operation()
def permission_remove(operation_logger, app, permission, force=False, sync_perm=True): def permission_delete(operation_logger, permission, force=False, sync_perm=True):
""" """
Remove a permission for a specific application Delete a permission
Keyword argument: Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
permission -- name of the permission ("main" by default)
""" """
if permission == "main" and not force: # By default, manipulate main permission
raise YunohostError('remove_main_permission_not_allowed') if "." not in permission:
permission = permission + ".main"
if permission.endswith(".main") and not force:
raise YunohostError('permission_cannot_remove_main')
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
# Make sure this permission exists
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
if not existing_permission:
raise YunohostError('permission_not_found', permission=permission)
# Actually delete the permission
operation_logger.related_to.append(('app', permission.split(".")[0]))
operation_logger.start() operation_logger.start()
if not ldap.remove('cn=%s,ou=permission' % str(permission + '.' + app)):
raise YunohostError('permission_deletion_failed', permission=permission, app=app) try:
ldap.remove('cn=%s,ou=permission' % permission)
except Exception as e:
raise YunohostError('permission_deletion_failed', permission=permission, error=e)
if sync_perm: if sync_perm:
permission_sync_to_user() permission_sync_to_user()
logger.debug(m18n.n('permission_deleted', permission=permission, app=app)) logger.debug(m18n.n('permission_deleted', permission=permission))
def permission_sync_to_user(force=False): def permission_sync_to_user():
""" """
Sychronise the inheritPermission attribut in the permission object from the Sychronise the inheritPermission attribut in the permission object from the
user<->group link and the group<->permission link user<->group link and the group<->permission link
Keyword argument:
force -- Force to recreate all attributes. Used generally with the
backup which uses "slapadd" which doesnt' use the memberOf overlay.
Note that by removing all value and adding a new time, we force the
overlay to update all attributes
""" """
# Note that a LDAP operation with the same value that is in LDAP crash SLAP.
# So we need to check before each ldap operation that we really change something in LDAP
import os import os
from yunohost.app import app_ssowatconf from yunohost.app import app_ssowatconf
from yunohost.user import user_group_list
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
permission_attrs = [ groups = user_group_list(full=True)["groups"]
'cn', permissions = user_permission_list(full=True)["permissions"]
'member',
]
group_info = ldap.search('ou=groups,dc=yunohost,dc=org',
'(objectclass=groupOfNamesYnh)', permission_attrs)
group_info = {g['cn'][0]: g for g in group_info}
for per in ldap.search('ou=permission,dc=yunohost,dc=org', for permission_name, permission_infos in permissions.items():
'(objectclass=permissionYnh)',
['cn', 'inheritPermission', 'groupPermission', 'memberUid']):
if 'groupPermission' not in per: # These are the users currently allowed because there's an 'inheritPermission' object corresponding to it
per['groupPermission'] = [] currently_allowed_users = set(permission_infos["corresponding_users"])
user_permission = set()
for group in per['groupPermission']: # These are the users that should be allowed because they are member of a group that is allowed for this permission ...
group = group.split("=")[1].split(",")[0] should_be_allowed_users = set([user for group in permission_infos["allowed"] for user in groups[group]["members"]])
if 'member' not in group_info[group]:
# Note that a LDAP operation with the same value that is in LDAP crash SLAP.
# So we need to check before each ldap operation that we really change something in LDAP
if currently_allowed_users == should_be_allowed_users:
# We're all good, this permission is already correctly synchronized !
continue continue
for user in group_info[group]['member']:
user_permission.add(user)
if 'inheritPermission' not in per: new_inherited_perms = {'inheritPermission': ["uid=%s,ou=users,dc=yunohost,dc=org" % u for u in should_be_allowed_users],
per['inheritPermission'] = [] 'memberUid': should_be_allowed_users}
if 'memberUid' not in per:
per['memberUid'] = []
uid_val = [v.split("=")[1].split(",")[0] for v in user_permission] # Commit the change with the new inherited stuff
if user_permission == set(per['inheritPermission']) and set(uid_val) == set(per['memberUid']) and not force: try:
continue ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms)
inheritPermission = {'inheritPermission': user_permission, 'memberUid': uid_val} except Exception as e:
if force: raise YunohostError('permission_update_failed', permission=permission_name, error=e)
if per['groupPermission']:
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': []}): logger.debug("The permission database has been resynchronized")
raise YunohostError('permission_update_failed_clear')
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': per['groupPermission']}):
raise YunohostError('permission_update_failed_populate')
if per['inheritPermission']:
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'inheritPermission': []}):
raise YunohostError('permission_update_failed_clear')
if user_permission:
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission):
raise YunohostError('permission_update_failed')
else:
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission):
raise YunohostError('permission_update_failed')
logger.debug(m18n.n('permission_generated'))
app_ssowatconf() app_ssowatconf()
# Reload unscd, otherwise the group ain't propagated to the LDAP database # Reload unscd, otherwise the group ain't propagated to the LDAP database
os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=passwd')
os.system('nscd --invalidate=group') 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

@ -10,7 +10,7 @@ from yunohost.app import _is_installed
from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount
from yunohost.domain import _get_maindomain from yunohost.domain import _get_maindomain
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
from yunohost.user import user_permission_list from yunohost.user import user_permission_list, user_create, user_list, user_delete
from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps
# Get main domain # Get main domain
@ -38,10 +38,10 @@ def setup_function(function):
add_archive_wordpress_from_2p4() add_archive_wordpress_from_2p4()
assert len(backup_list()["archives"]) == 1 assert len(backup_list()["archives"]) == 1
if "with_backup_legacy_app_installed" in markers: if "with_legacy_app_installed" in markers:
assert not app_is_installed("backup_legacy_app") assert not app_is_installed("legacy_app")
install_app("backup_legacy_app_ynh", "/yolo") install_app("legacy_app_ynh", "/yolo")
assert app_is_installed("backup_legacy_app") assert app_is_installed("legacy_app")
if "with_backup_recommended_app_installed" in markers: if "with_backup_recommended_app_installed" in markers:
assert not app_is_installed("backup_recommended_app") assert not app_is_installed("backup_recommended_app")
@ -59,6 +59,13 @@ def setup_function(function):
add_archive_system_from_2p4() add_archive_system_from_2p4()
assert len(backup_list()["archives"]) == 1 assert len(backup_list()["archives"]) == 1
if "with_permission_app_installed" in markers:
assert not app_is_installed("permissions_app")
user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh")
install_app("permissions_app_ynh", "/urlpermissionapp"
"&admin=alice")
assert app_is_installed("permissions_app")
def teardown_function(function): def teardown_function(function):
@ -73,6 +80,9 @@ def teardown_function(function):
if "clean_opt_dir" in markers: if "clean_opt_dir" in markers:
shutil.rmtree("/opt/test_backup_output_directory") shutil.rmtree("/opt/test_backup_output_directory")
if "alice" in user_list()["users"]:
user_delete("alice")
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def check_LDAP_db_integrity_call(): def check_LDAP_db_integrity_call():
@ -92,6 +102,9 @@ def check_permission_for_apps_call():
def app_is_installed(app): def app_is_installed(app):
if app == "permissions_app":
return _is_installed(app)
# These are files we know should be installed by the app # These are files we know should be installed by the app
app_files = [] app_files = []
app_files.append("/etc/nginx/conf.d/%s.d/%s.conf" % (maindomain, app)) app_files.append("/etc/nginx/conf.d/%s.d/%s.conf" % (maindomain, app))
@ -105,7 +118,7 @@ def backup_test_dependencies_are_met():
# Dummy test apps (or backup archives) # Dummy test apps (or backup archives)
assert os.path.exists("./tests/apps/backup_wordpress_from_2p4") assert os.path.exists("./tests/apps/backup_wordpress_from_2p4")
assert os.path.exists("./tests/apps/backup_legacy_app_ynh") assert os.path.exists("./tests/apps/legacy_app_ynh")
assert os.path.exists("./tests/apps/backup_recommended_app_ynh") assert os.path.exists("./tests/apps/backup_recommended_app_ynh")
return True return True
@ -155,14 +168,9 @@ def delete_all_backups():
def uninstall_test_apps_if_needed(): def uninstall_test_apps_if_needed():
if _is_installed("backup_legacy_app"): for app in ["legacy_app", "backup_recommended_app", "wordpress", "permissions_app"]:
app_remove("backup_legacy_app") if _is_installed(app):
app_remove(app)
if _is_installed("backup_recommended_app"):
app_remove("backup_recommended_app")
if _is_installed("wordpress"):
app_remove("wordpress")
def install_app(app, path, additionnal_args=""): def install_app(app, path, additionnal_args=""):
@ -497,10 +505,10 @@ def test_restore_app_already_installed(mocker):
assert _is_installed("wordpress") assert _is_installed("wordpress")
@pytest.mark.with_backup_legacy_app_installed @pytest.mark.with_legacy_app_installed
def test_backup_and_restore_legacy_app(): def test_backup_and_restore_legacy_app():
_test_backup_and_restore_app("backup_legacy_app") _test_backup_and_restore_app("legacy_app")
@pytest.mark.with_backup_recommended_app_installed @pytest.mark.with_backup_recommended_app_installed
@ -514,6 +522,35 @@ def test_backup_and_restore_with_ynh_restore():
_test_backup_and_restore_app("backup_recommended_app") _test_backup_and_restore_app("backup_recommended_app")
@pytest.mark.with_permission_app_installed
def test_backup_and_restore_permission_app():
res = user_permission_list(full=True)['permissions']
assert "permissions_app.main" in res
assert "permissions_app.admin" in res
assert "permissions_app.dev" in res
assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"]
assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"]
assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"]
assert res['permissions_app.main']['allowed'] == ["all_users"]
assert res['permissions_app.admin']['allowed'] == ["alice"]
assert res['permissions_app.dev']['allowed'] == []
_test_backup_and_restore_app("permissions_app")
res = user_permission_list(full=True)['permissions']
assert "permissions_app.main" in res
assert "permissions_app.admin" in res
assert "permissions_app.dev" in res
assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"]
assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"]
assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"]
assert res['permissions_app.main']['allowed'] == ["all_users"]
assert res['permissions_app.admin']['allowed'] == ["alice"]
assert res['permissions_app.dev']['allowed'] == []
def _test_backup_and_restore_app(app): def _test_backup_and_restore_app(app):
@ -531,7 +568,7 @@ def _test_backup_and_restore_app(app):
# Uninstall the app # Uninstall the app
app_remove(app) app_remove(app)
assert not app_is_installed(app) assert not app_is_installed(app)
assert app not in user_permission_list()['permissions'] assert app+".main" not in user_permission_list()['permissions']
# Restore the app # Restore the app
backup_restore(system=None, name=archives[0], backup_restore(system=None, name=archives[0],
@ -541,8 +578,7 @@ def _test_backup_and_restore_app(app):
# Check permission # Check permission
per_list = user_permission_list()['permissions'] per_list = user_permission_list()['permissions']
assert app in per_list assert app+".main" in per_list
assert "main" in per_list[app]
# #
# Some edge cases # # Some edge cases #

View file

@ -1,9 +1,11 @@
import pytest import pytest
from moulinette.core import MoulinetteError from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map
from yunohost.app import app_install, app_remove, app_change_url, app_list
from yunohost.user import user_list, user_create, user_permission_list, user_delete, user_group_list, user_group_delete, user_permission_add, user_permission_remove, user_permission_clear from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \
from yunohost.permission import permission_add, permission_update, permission_remove user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info
from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \
permission_create, permission_urls, permission_delete
from yunohost.domain import _get_maindomain from yunohost.domain import _get_maindomain
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
@ -18,20 +20,18 @@ def clean_user_groups_permission():
if g != "all_users": if g != "all_users":
user_group_delete(g) user_group_delete(g)
for a, per in user_permission_list()['permissions'].items(): for p in user_permission_list()['permissions']:
if a in ['wiki', 'blog', 'site']: if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]):
for p in per: permission_delete(p, force=True, sync_perm=False)
permission_remove(a, p, force=True, sync_perm=False)
def setup_function(function): def setup_function(function):
clean_user_groups_permission() clean_user_groups_permission()
user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh")
user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh")
permission_add("wiki", "main", [maindomain + "/wiki"], sync_perm=False) permission_create("wiki.main", urls=[maindomain + "/wiki"], sync_perm=False)
permission_add("blog", "main", sync_perm=False) permission_create("blog.main", sync_perm=False)
user_permission_update("blog.main", remove="all_users", add="alice")
user_permission_add(["blog"], "main", group="alice")
def teardown_function(function): def teardown_function(function):
clean_user_groups_permission() clean_user_groups_permission()
@ -57,7 +57,7 @@ def check_LDAP_db_integrity():
# One part should be done automatically by the "memberOf" overlay of LDAP. # One part should be done automatically by the "memberOf" overlay of LDAP.
# The other part is done by the the "permission_sync_to_user" function of the permission module # The other part is done by the the "permission_sync_to_user" function of the permission module
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
user_search = ldap.search('ou=users,dc=yunohost,dc=org', user_search = ldap.search('ou=users,dc=yunohost,dc=org',
@ -76,161 +76,155 @@ def check_LDAP_db_integrity():
for user in user_search: for user in user_search:
user_dn = 'uid=' + user['uid'][0] + ',ou=users,dc=yunohost,dc=org' user_dn = 'uid=' + user['uid'][0] + ',ou=users,dc=yunohost,dc=org'
group_list = [m.split("=")[1].split(",")[0] for m in user['memberOf']] group_list = [_ldap_path_extract(m, "cn") for m in user['memberOf']]
permission_list = [] permission_list = [_ldap_path_extract(m, "cn") for m in user.get('permission', [])]
if 'permission' in user:
permission_list = [m.split("=")[1].split(",")[0] for m in user['permission']]
# This user's DN sould be found in all groups it is a member of
for group in group_list: for group in group_list:
assert user_dn in group_map[group]['member'] assert user_dn in group_map[group]['member']
# This user's DN should be found in all perms it has access to
for permission in permission_list: for permission in permission_list:
assert user_dn in permission_map[permission]['inheritPermission'] assert user_dn in permission_map[permission]['inheritPermission']
for permission in permission_search: for permission in permission_search:
permission_dn = 'cn=' + permission['cn'][0] + ',ou=permission,dc=yunohost,dc=org' permission_dn = 'cn=' + permission['cn'][0] + ',ou=permission,dc=yunohost,dc=org'
user_list = []
group_list = []
if 'inheritPermission' in permission:
user_list = [m.split("=")[1].split(",")[0] for m in permission['inheritPermission']]
assert set(user_list) == set(permission['memberUid'])
if 'groupPermission' in permission:
group_list = [m.split("=")[1].split(",")[0] for m in permission['groupPermission']]
# inheritPermission uid's should match memberUids
user_list = [_ldap_path_extract(m, "uid") for m in permission.get('inheritPermission', [])]
assert set(user_list) == set(permission.get('memberUid', []))
# This perm's DN should be found on all related users it is related to
for user in user_list: for user in user_list:
assert permission_dn in user_map[user]['permission'] assert permission_dn in user_map[user]['permission']
# Same for groups : we should find the permission's DN for all related groups
group_list = [_ldap_path_extract(m, "cn") for m in permission.get('groupPermission', [])]
for group in group_list: for group in group_list:
assert permission_dn in group_map[group]['permission'] assert permission_dn in group_map[group]['permission']
if 'member' in group_map[group]:
user_list_in_group = [m.split("=")[1].split(",")[0] for m in group_map[group]['member']] # The list of user in the group should be a subset of all users related to the current permission
assert set(user_list_in_group) <= set(user_list) users_in_group = [_ldap_path_extract(m, "uid") for m in group_map[group].get("member", [])]
assert set(users_in_group) <= set(user_list)
for group in group_search: for group in group_search:
group_dn = 'cn=' + group['cn'][0] + ',ou=groups,dc=yunohost,dc=org' group_dn = 'cn=' + group['cn'][0] + ',ou=groups,dc=yunohost,dc=org'
user_list = []
permission_list = []
if 'member' in group:
user_list = [m.split("=")[1].split(",")[0] for m in group['member']]
if group['cn'][0] in user_list:
# If it's the main group of the user it's normal that it is not in the memberUid
g_list = list(user_list)
g_list.remove(group['cn'][0])
if 'memberUid' in group:
assert set(g_list) == set(group['memberUid'])
else:
assert g_list == []
else:
assert set(user_list) == set(group['memberUid'])
if 'permission' in group:
permission_list = [m.split("=")[1].split(",")[0] for m in group['permission']]
user_list = [_ldap_path_extract(m, "uid") for m in group.get("member", [])]
# For primary groups, we should find that :
# - len(user_list) is 1 (a primary group has only 1 member)
# - the group name should be an existing yunohost user
# - memberUid is empty (meaning no other member than the corresponding user)
if group['cn'][0] in user_list:
assert len(user_list) == 1
assert group["cn"][0] in user_map
assert group.get('memberUid', []) == []
# Otherwise, user_list and memberUid should have the same content
else:
assert set(user_list) == set(group.get('memberUid', []))
# For all users members, this group should be in the "memberOf" on the other side
for user in user_list: for user in user_list:
assert group_dn in user_map[user]['memberOf'] assert group_dn in user_map[user]['memberOf']
# For all the permissions of this group, the group should be among the "groupPermission" on the other side
permission_list = [_ldap_path_extract(m, "cn") for m in group.get('permission', [])]
for permission in permission_list: for permission in permission_list:
assert group_dn in permission_map[permission]['groupPermission'] assert group_dn in permission_map[permission]['groupPermission']
if 'inheritPermission' in permission_map:
allowed_user_list = [m.split("=")[1].split(",")[0] for m in permission_map[permission]['inheritPermission']] # And the list of user of this group (user_list) should be a subset of all allowed users for this perm...
allowed_user_list = [_ldap_path_extract(m, "uid") for m in permission_map[permission].get('inheritPermission', [])]
assert set(user_list) <= set(allowed_user_list) assert set(user_list) <= set(allowed_user_list)
def check_permission_for_apps(): def check_permission_for_apps():
# We check that the for each installed apps we have at last the "main" permission # We check that the for each installed apps we have at last the "main" permission
# and we don't have any permission linked to no apps. The only exception who is not liked to an app # and we don't have any permission linked to no apps. The only exception who is not liked to an app
# is mail, metronome, and sftp # is mail, xmpp, and sftp
from yunohost.utils.ldap import _get_ldap_interface app_perms = user_permission_list(ignore_system_perms=True)["permissions"].keys()
ldap = _get_ldap_interface()
permission_search = ldap.search('ou=permission,dc=yunohost,dc=org', # Keep only the prefix so that
'(objectclass=permissionYnh)', # ["foo.main", "foo.pwet", "bar.main"]
['cn', 'groupPermission', 'inheritPermission', 'memberUid']) # becomes
# {"bar", "foo"}
# and compare this to the list of installed apps ...
app_perms_prefix = set(p.split(".")[0] for p in app_perms)
installed_apps = {app['id'] for app in app_list(installed=True)['apps']} installed_apps = {app['id'] for app in app_list(installed=True)['apps']}
permission_list_set = {permission['cn'][0].split(".")[1] for permission in permission_search}
extra_service_permission = set(['mail', 'metronome']) assert installed_apps == app_perms_prefix
if 'sftp' in permission_list_set:
extra_service_permission.add('sftp')
assert installed_apps == permission_list_set - extra_service_permission
# #
# List functions # List functions
# #
def test_list_permission(): def test_permission_list():
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert "wiki" in res assert "wiki.main" in res
assert "main" in res['wiki'] assert "blog.main" in res
assert "blog" in res assert "mail.main" in res
assert "main" in res['blog'] assert "xmpp.main" in res
assert "mail" in res assert res['wiki.main']['allowed'] == ["all_users"]
assert "main" in res['mail'] assert res['blog.main']['allowed'] == ["alice"]
assert "metronome" in res assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"])
assert "main" in res['metronome'] assert res['blog.main']['corresponding_users'] == ["alice"]
assert ["all_users"] == res['wiki']['main']['allowed_groups'] assert res['wiki.main']['urls'] == [maindomain + "/wiki"]
assert ["alice"] == res['blog']['main']['allowed_groups']
assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users'])
assert ["alice"] == res['blog']['main']['allowed_users']
assert [maindomain + "/wiki"] == res['wiki']['main']['URL']
# #
# Create - Remove functions # Create - Remove functions
# #
def test_add_permission_1(): def test_permission_create_main():
permission_add("site", "test") permission_create("site.main")
res = user_permission_list(full=True)['permissions']
assert "site.main" in res
assert res['site.main']['allowed'] == ["all_users"]
assert set(res['site.main']['corresponding_users']) == set(["alice", "bob"])
def test_permission_create_extra():
permission_create("site.test")
res = user_permission_list(full=True)['permissions']
assert "site.test" in res
# all_users is only enabled by default on .main perms
assert "all_users" not in res['site.test']['allowed']
assert res['site.test']['corresponding_users'] == []
def test_permission_delete():
permission_delete("wiki.main", force=True)
res = user_permission_list()['permissions'] res = user_permission_list()['permissions']
assert "site" in res assert "wiki.main" not in res
assert "test" in res['site']
assert "all_users" in res['site']['test']['allowed_groups']
assert set(["alice", "bob"]) == set(res['site']['test']['allowed_users'])
def test_add_permission_2():
permission_add("site", "main", default_allow=False)
res = user_permission_list()['permissions']
assert "site" in res
assert "main" in res['site']
assert [] == res['site']['main']['allowed_groups']
assert [] == res['site']['main']['allowed_users']
def test_remove_permission():
permission_remove("wiki", "main", force=True)
res = user_permission_list()['permissions']
assert "wiki" not in res
# #
# Error on create - remove function # Error on create - remove function
# #
def test_add_bad_permission(): def test_permission_create_already_existing():
# Create permission with same name
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
permission_add("wiki", "main") permission_create("wiki.main")
def test_remove_bad_permission(): def test_permission_delete_doesnt_existing():
# Remove not existant permission with pytest.raises(YunohostError):
with pytest.raises(MoulinetteError): permission_delete("doesnt.exist", force=True)
permission_remove("non_exit", "main", force=True)
res = user_permission_list()['permissions'] res = user_permission_list()['permissions']
assert "wiki" in res assert "wiki.main" in res
assert "main" in res['wiki'] assert "blog.main" in res
assert "blog" in res assert "mail.main" in res
assert "main" in res['blog'] assert "xmpp.main" in res
assert "mail" in res
assert "main" in res ['mail']
assert "metronome" in res
assert "main" in res['metronome']
def test_remove_main_permission(): def test_permission_delete_main_without_force():
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
permission_remove("blog", "main") permission_delete("blog.main")
res = user_permission_list()['permissions'] res = user_permission_list()['permissions']
assert "mail" in res assert "blog.main" in res
assert "main" in res['mail']
# #
# Update functions # Update functions
@ -238,182 +232,154 @@ def test_remove_main_permission():
# user side functions # user side functions
def test_allow_first_group(): def test_permission_add_group():
# Remove permission to all_users and define per users user_permission_update("wiki.main", add="alice")
user_permission_add(["wiki"], "main", group="alice")
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert ['alice'] == res['wiki']['main']['allowed_users'] assert set(res['wiki.main']['allowed']) == set(["all_users", "alice"])
assert ['alice'] == res['wiki']['main']['allowed_groups'] assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"])
def test_allow_other_group(): def test_permission_remove_group():
# Allow new user in a permission user_permission_update("blog.main", remove="alice")
user_permission_add(["blog"], "main", group="bob")
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) assert res['blog.main']['allowed'] == []
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_groups']) assert res['blog.main']['corresponding_users'] == []
def test_disallow_group_1(): def test_permission_add_and_remove_group():
# Disallow a user in a permission user_permission_update("wiki.main", add="alice", remove="all_users")
user_permission_remove(["blog"], "main", group="alice")
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert [] == res['blog']['main']['allowed_users'] assert res['wiki.main']['allowed'] == ["alice"]
assert [] == res['blog']['main']['allowed_groups'] assert res['wiki.main']['corresponding_users'] == ["alice"]
def test_allow_group_1(): def test_permission_add_group_already_allowed():
# Allow a user when he is already allowed user_permission_update("blog.main", add="alice")
user_permission_add(["blog"], "main", group="alice")
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert ["alice"] == res['blog']['main']['allowed_users'] assert res['blog.main']['allowed'] == ["alice"]
assert ["alice"] == res['blog']['main']['allowed_groups'] assert res['blog.main']['corresponding_users'] == ["alice"]
def test_disallow_group_1(): def test_permission_remove_group_already_not_allowed():
# Disallow a user when he is already disallowed user_permission_update("blog.main", remove="bob")
user_permission_remove(["blog"], "main", group="bob")
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert ["alice"] == res['blog']['main']['allowed_users'] assert res['blog.main']['allowed'] == ["alice"]
assert ["alice"] == res['blog']['main']['allowed_groups'] assert res['blog.main']['corresponding_users'] == ["alice"]
def test_reset_permission(): def test_permission_reset():
# Reset permission # Reset permission
user_permission_clear(["blog"], "main") user_permission_reset("blog.main")
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) assert res['blog.main']['allowed'] == ["all_users"]
assert ["all_users"] == res['blog']['main']['allowed_groups'] assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"])
# internal functions
def test_add_url_1():
# Add URL in permission which hasn't any URL defined
permission_update("blog", "main", add_url=[maindomain + "/testA"])
res = user_permission_list()['permissions']
assert [maindomain + "/testA"] == res['blog']['main']['URL']
def test_add_url_2():
# Add a second URL in a permission
permission_update("wiki", "main", add_url=[maindomain + "/testA"])
res = user_permission_list()['permissions']
assert set([maindomain + "/testA", maindomain + "/wiki"]) == set(res['wiki']['main']['URL'])
def test_remove_url_1():
permission_update("wiki", "main", remove_url=[maindomain + "/wiki"])
res = user_permission_list()['permissions']
assert 'URL' not in res['wiki']['main']
def test_add_url_3():
# Add a url already added
permission_update("wiki", "main", add_url=[maindomain + "/wiki"])
res = user_permission_list()['permissions']
assert [maindomain + "/wiki"] == res['wiki']['main']['URL']
def test_remove_url_2():
# Remove a url not added (with a permission which contain some URL)
permission_update("wiki", "main", remove_url=[maindomain + "/not_exist"])
res = user_permission_list()['permissions']
assert [maindomain + "/wiki"] == res['wiki']['main']['URL']
def test_remove_url_2():
# Remove a url not added (with a permission which contain no URL)
permission_update("blog", "main", remove_url=[maindomain + "/not_exist"])
res = user_permission_list()['permissions']
assert 'URL' not in res['blog']['main']
# #
# Error on update function # Error on update function
# #
def test_disallow_bad_group_1(): def test_permission_add_group_that_doesnt_exist():
# Disallow a group when the group all_users is allowed
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_permission_remove("wiki", "main", group="alice") user_permission_update("blog.main", add="doesnt_exist")
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert ["all_users"] == res['wiki']['main']['allowed_groups'] assert res['blog.main']['allowed'] == ["alice"]
assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) assert res['blog.main']['corresponding_users'] == ["alice"]
def test_allow_bad_user(): def test_permission_update_permission_that_doesnt_exist():
# Allow a non existant group
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_permission_add(["blog"], "main", group="not_exist") user_permission_update("doesnt.exist", add="alice")
res = user_permission_list()['permissions']
assert ["alice"] == res['blog']['main']['allowed_groups']
assert ["alice"] == res['blog']['main']['allowed_users']
def test_disallow_bad_group_2(): # Permission url management
# Disallow a non existant group
with pytest.raises(YunohostError):
user_permission_remove(["blog"], "main", group="not_exist")
res = user_permission_list()['permissions'] def test_permission_add_url():
assert ["alice"] == res['blog']['main']['allowed_groups'] permission_urls("blog.main", add=[maindomain + "/testA"])
assert ["alice"] == res['blog']['main']['allowed_users']
def test_allow_bad_permission_1(): res = user_permission_list(full=True)['permissions']
# Allow a user to a non existant permission assert res["blog.main"]["urls"] == [maindomain + "/testA"]
with pytest.raises(YunohostError):
user_permission_add(["wiki"], "not_exit", group="alice")
def test_allow_bad_permission_2(): def test_permission_add_second_url():
# Allow a user to a non existant permission permission_urls("wiki.main", add=[maindomain + "/testA"])
with pytest.raises(YunohostError):
user_permission_add(["not_exit"], "main", group="alice") res = user_permission_list(full=True)['permissions']
assert set(res["wiki.main"]["urls"]) == set([maindomain + "/testA", maindomain + "/wiki"])
def test_permission_remove_url():
permission_urls("wiki.main", remove=[maindomain + "/wiki"])
res = user_permission_list(full=True)['permissions']
assert res["wiki.main"]["urls"] == []
def test_permission_add_url_already_added():
res = user_permission_list(full=True)['permissions']
assert res["wiki.main"]["urls"] == [maindomain + "/wiki"]
permission_urls("wiki.main", add=[maindomain + "/wiki"])
res = user_permission_list(full=True)['permissions']
assert res["wiki.main"]["urls"] == [maindomain + "/wiki"]
def test_permission_remove_url_not_added():
permission_urls("wiki.main", remove=[maindomain + "/doesnt_exist"])
res = user_permission_list(full=True)['permissions']
assert res['wiki.main']['urls'] == [maindomain + "/wiki"]
# #
# Application interaction # Application interaction
# #
def test_install_app(): def test_permission_app_install():
app_install("./tests/apps/permissions_app_ynh", app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert "permissions_app" in res assert "permissions_app.main" in res
assert "main" in res['permissions_app'] assert "permissions_app.admin" in res
assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL'] assert "permissions_app.dev" in res
assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL'] assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"]
assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL'] assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"]
assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"]
assert ["all_users"] == res['permissions_app']['main']['allowed_groups'] assert res['permissions_app.main']['allowed'] == ["all_users"]
assert set(["alice", "bob"]) == set(res['permissions_app']['main']['allowed_users']) assert set(res['permissions_app.main']['corresponding_users']) == set(["alice", "bob"])
assert ["alice"] == res['permissions_app']['admin']['allowed_groups'] assert res['permissions_app.admin']['allowed'] == ["alice"]
assert ["alice"] == res['permissions_app']['admin']['allowed_users'] assert res['permissions_app.admin']['corresponding_users'] == ["alice"]
assert ["all_users"] == res['permissions_app']['dev']['allowed_groups'] assert res['permissions_app.dev']['allowed'] == []
assert set(["alice", "bob"]) == set(res['permissions_app']['dev']['allowed_users']) assert set(res['permissions_app.dev']['corresponding_users']) == set()
def test_remove_app(): # Check that we get the right stuff in app_map, which is used to generate the ssowatconf
assert maindomain + "/urlpermissionapp" in app_map(user="alice").keys()
user_permission_update("permissions_app.main", remove="all_users", add="bob")
assert maindomain + "/urlpermissionapp" not in app_map(user="alice").keys()
assert maindomain + "/urlpermissionapp" in app_map(user="bob").keys()
def test_permission_app_remove():
app_install("./tests/apps/permissions_app_ynh", app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
app_remove("permissions_app") app_remove("permissions_app")
res = user_permission_list()['permissions'] # Check all permissions for this app got deleted
assert "permissions_app" not in res res = user_permission_list(full=True)['permissions']
assert not any(p.startswith("permissions_app.") for p in res.keys())
def test_change_url(): def test_permission_app_change_url():
app_install("./tests/apps/permissions_app_ynh", app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL'] assert res['permissions_app.main']['urls'] == [maindomain + "/urlpermissionapp"]
assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL'] assert res['permissions_app.admin']['urls'] == [maindomain + "/urlpermissionapp/admin"]
assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL'] assert res['permissions_app.dev']['urls'] == [maindomain + "/urlpermissionapp/dev"]
app_change_url("permissions_app", maindomain, "/newchangeurl") app_change_url("permissions_app", maindomain, "/newchangeurl")
res = user_permission_list()['permissions'] res = user_permission_list(full=True)['permissions']
assert [maindomain + "/newchangeurl"] == res['permissions_app']['main']['URL'] assert res['permissions_app.main']['urls'] == [maindomain + "/newchangeurl"]
assert [maindomain + "/newchangeurl/admin"] == res['permissions_app']['admin']['URL'] assert res['permissions_app.admin']['urls'] == [maindomain + "/newchangeurl/admin"]
assert [maindomain + "/newchangeurl/dev"] == res['permissions_app']['dev']['URL'] assert res['permissions_app.dev']['urls'] == [maindomain + "/newchangeurl/dev"]

View file

@ -1,7 +1,7 @@
import pytest import pytest
from moulinette.core import MoulinetteError from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \
from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_add, user_group_delete, user_group_update, user_group_info user_group_list, user_group_create, user_group_delete, user_group_update, user_group_info
from yunohost.domain import _get_maindomain from yunohost.domain import _get_maindomain
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
from yunohost.tests.test_permission import check_LDAP_db_integrity from yunohost.tests.test_permission import check_LDAP_db_integrity
@ -24,10 +24,10 @@ def setup_function(function):
user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh")
user_create("jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh") user_create("jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh")
user_group_add("dev") user_group_create("dev")
user_group_add("apps") user_group_create("apps")
user_group_update("dev", add_user=["alice"]) user_group_update("dev", add=["alice"])
user_group_update("apps", add_user=["bob"]) user_group_update("apps", add=["bob"])
def teardown_function(function): def teardown_function(function):
clean_user_groups() clean_user_groups()
@ -82,12 +82,13 @@ def test_del_user():
assert "alice" not in group_res assert "alice" not in group_res
assert "alice" not in group_res['all_users']['members'] assert "alice" not in group_res['all_users']['members']
def test_add_group(): def test_create_group():
user_group_add("adminsys") user_group_create("adminsys")
group_res = user_group_list()['groups'] group_res = user_group_list()['groups']
assert "adminsys" in group_res assert "adminsys" in group_res
assert "members" not in group_res['adminsys'] assert "members" in group_res['adminsys'].keys()
assert group_res["adminsys"]["members"] == []
def test_del_group(): def test_del_group():
user_group_delete("dev") user_group_delete("dev")
@ -99,112 +100,106 @@ def test_del_group():
# Error on create / remove function # Error on create / remove function
# #
def test_add_bad_user_1(): def test_create_user_with_mail_address_already_taken():
# Check email already exist with pytest.raises(YunohostError):
with pytest.raises(MoulinetteError):
user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh") user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh")
def test_add_bad_user_2(): def test_create_user_with_password_too_simple():
# Check to short password with pytest.raises(YunohostError):
with pytest.raises(MoulinetteError):
user_create("other", "Alice", "White", "other@" + maindomain, "12") user_create("other", "Alice", "White", "other@" + maindomain, "12")
def test_add_bad_user_3(): def test_create_user_already_exists():
# Check user already exist with pytest.raises(YunohostError):
with pytest.raises(MoulinetteError):
user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh") user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh")
def test_del_bad_user_1(): def test_update_user_with_mail_address_already_taken():
# Check user not found with pytest.raises(YunohostError):
with pytest.raises(MoulinetteError): user_update("bob", add_mailalias="alice@" + maindomain)
user_delete("not_exit")
def test_add_bad_group_1(): def test_del_user_that_does_not_exist():
with pytest.raises(YunohostError):
user_delete("doesnt_exist")
def test_create_group_all_users():
# Check groups already exist with special group "all_users" # Check groups already exist with special group "all_users"
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_group_add("all_users") user_group_create("all_users")
def test_add_bad_group_2(): def test_create_group_already_exists():
# Check groups already exist (for standard groups) # Check groups already exist (regular groups)
with pytest.raises(MoulinetteError): with pytest.raises(YunohostError):
user_group_add("dev") user_group_create("dev")
def test_del_bad_group_1(): def test_del_group_all_users():
# Check not allowed to remove this groups
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_group_delete("all_users") user_group_delete("all_users")
def test_del_bad_group_2(): def test_del_group_that_does_not_exist():
# Check groups not found with pytest.raises(YunohostError):
with pytest.raises(MoulinetteError): user_group_delete("doesnt_exist")
user_group_delete("not_exit")
# #
# Update function # Update function
# #
def test_update_user_1(): def test_update_user():
user_update("alice", firstname="NewName", lastname="NewLast") user_update("alice", firstname="NewName", lastname="NewLast")
info = user_info("alice") info = user_info("alice")
assert "NewName" == info['firstname'] assert info['firstname'] == "NewName"
assert "NewLast" == info['lastname'] assert info['lastname'] == "NewLast"
def test_update_group_1(): def test_update_group_add_user():
user_group_update("dev", add_user=["bob"]) user_group_update("dev", add=["bob"])
group_res = user_group_list()['groups'] group_res = user_group_list()['groups']
assert set(["alice", "bob"]) == set(group_res['dev']['members']) assert set(group_res['dev']['members']) == set(["alice", "bob"])
def test_update_group_2(): def test_update_group_add_user_already_in():
# Try to add a user in a group when the user is already in user_group_update("apps", add=["bob"])
user_group_update("apps", add_user=["bob"])
group_res = user_group_list()['groups'] group_res = user_group_list()['groups']
assert ["bob"] == group_res['apps']['members'] assert group_res['apps']['members'] == ["bob"]
def test_update_group_3(): def test_update_group_remove_user():
# Try to remove a user in a group user_group_update("apps", remove=["bob"])
user_group_update("apps", remove_user=["bob"])
group_res = user_group_list()['groups'] group_res = user_group_list()['groups']
assert "members" not in group_res['apps'] assert group_res['apps']['members'] == []
def test_update_group_4(): def test_update_group_remove_user_not_already_in():
# Try to remove a user in a group when it is not already in user_group_update("apps", remove=["jack"])
user_group_update("apps", remove_user=["jack"])
group_res = user_group_list()['groups'] group_res = user_group_list()['groups']
assert ["bob"] == group_res['apps']['members'] assert group_res['apps']['members'] == ["bob"]
# #
# Error on update functions # Error on update functions
# #
def test_bad_update_user_1(): def test_update_user_that_doesnt_exist():
# Check user not found
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_update("not_exit", firstname="NewName", lastname="NewLast") user_update("doesnt_exist", firstname="NewName", lastname="NewLast")
def test_update_group_that_doesnt_exist():
def bad_update_group_1():
# Check groups not found # Check groups not found
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_group_update("not_exit", add_user=["alice"]) user_group_update("doesnt_exist", add=["alice"])
def test_bad_update_group_2(): def test_update_group_all_users_manually():
# Check remove user in groups "all_users" not allowed
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_group_update("all_users", remove_user=["alice"]) user_group_update("all_users", remove=["alice"])
def test_bad_update_group_3(): assert "alice" in user_group_list()["groups"]["all_users"]["members"]
# Check remove user in it own group not allowed
def test_update_group_primary_manually():
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_group_update("alice", remove_user=["alice"]) user_group_update("alice", remove=["alice"])
assert "alice" in user_group_list()["groups"]["alice"]["members"]
def test_bad_update_group_1(): def test_update_group_add_user_that_doesnt_exist():
# Check add bad user in group # Check add bad user in group
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
user_group_update("dev", add_user=["not_exist"]) user_group_update("dev", add=["doesnt_exist"])
assert "not_exist" not in user_group_list()["groups"]["dev"] assert "doesnt_exist" not in user_group_list()["groups"]["dev"]["members"]

View file

@ -32,6 +32,7 @@ import crypt
import random import random
import string import string
import subprocess import subprocess
import copy
from moulinette import m18n from moulinette import m18n
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
@ -126,12 +127,18 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
if username in user_list()["users"]:
raise YunohostError("user_already_exists", user=username)
# Validate uniqueness of username and mail in LDAP # Validate uniqueness of username and mail in LDAP
try:
ldap.validate_uniqueness({ ldap.validate_uniqueness({
'uid': username, 'uid': username,
'mail': mail, 'mail': mail,
'cn': username 'cn': username
}) })
except Exception as e:
raise YunohostError('user_creation_failed', user=username, error=e)
# Validate uniqueness of username in system users # Validate uniqueness of username in system users
all_existing_usernames = {x.pw_name for x in pwd.getpwall()} all_existing_usernames = {x.pw_name for x in pwd.getpwall()}
@ -204,7 +211,11 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
except IOError as e: except IOError as e:
raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror) raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror)
if ldap.add('uid=%s,ou=users' % username, attr_dict): try:
ldap.add('uid=%s,ou=users' % username, attr_dict)
except Exception as e:
raise YunohostError('user_creation_failed', user=username, error=e)
# Invalidate passwd to take user creation into account # Invalidate passwd to take user creation into account
subprocess.call(['nscd', '-i', 'passwd']) subprocess.call(['nscd', '-i', 'passwd'])
@ -218,9 +229,8 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
exc_info=1) exc_info=1)
# Create group for user and add to group 'all_users' # Create group for user and add to group 'all_users'
user_group_add(groupname=username, gid=uid, sync_perm=False) user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False)
user_group_update(groupname=username, add_user=username, force=True, sync_perm=False) user_group_update(groupname='all_users', add=username, force=True, sync_perm=True)
user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=True)
# TODO: Send a welcome mail to user # TODO: Send a welcome mail to user
logger.success(m18n.n('user_created')) logger.success(m18n.n('user_created'))
@ -230,8 +240,6 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
return {'fullname': fullname, 'username': username, 'mail': mail} return {'fullname': fullname, 'username': username, 'mail': mail}
raise YunohostError('user_creation_failed')
@is_unit_operation([('username', 'user')]) @is_unit_operation([('username', 'user')])
def user_delete(operation_logger, username, purge=False): def user_delete(operation_logger, username, purge=False):
@ -245,32 +253,35 @@ def user_delete(operation_logger, username, purge=False):
""" """
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
from yunohost.permission import permission_sync_to_user
if username not in user_list()["users"]:
raise YunohostError('user_unknown', user=username)
operation_logger.start() operation_logger.start()
user_group_update("all_users", remove=username, force=True, sync_perm=False)
for group, infos in user_group_list()["groups"].items():
if group == "all_users":
continue
# If the user is in this group (and it's not the primary group),
# remove the member from the group
if username != group and username in infos["members"]:
user_group_update(group, remove=username, sync_perm=False)
user_group_delete(username, force=True, sync_perm=True)
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
if ldap.remove('uid=%s,ou=users' % username): try:
ldap.remove('uid=%s,ou=users' % username)
except Exception as e:
raise YunohostError('user_deletion_failed', user=username, error=e)
# Invalidate passwd to take user deletion into account # Invalidate passwd to take user deletion into account
subprocess.call(['nscd', '-i', 'passwd']) subprocess.call(['nscd', '-i', 'passwd'])
if purge: if purge:
subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)])
else:
raise YunohostError('user_deletion_failed')
user_group_delete(username, force=True, sync_perm=True)
group_list = ldap.search('ou=groups,dc=yunohost,dc=org',
'(&(objectclass=groupOfNamesYnh)(memberUid=%s))'
% username, ['cn'])
for group in group_list:
user_list = ldap.search('ou=groups,dc=yunohost,dc=org',
'cn=' + group['cn'][0],
['memberUid'])[0]
user_list['memberUid'].remove(username)
if not ldap.update('cn=%s,ou=groups' % group['cn'][0], user_list):
raise YunohostError('group_update_failed')
hook_callback('post_user_delete', args=[username, purge]) hook_callback('post_user_delete', args=[username, purge])
@ -338,7 +349,10 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail=
'webmaster@' + main_domain, 'webmaster@' + main_domain,
'postmaster@' + main_domain, 'postmaster@' + main_domain,
] ]
try:
ldap.validate_uniqueness({'mail': mail}) ldap.validate_uniqueness({'mail': mail})
except Exception as e:
raise YunohostError('user_update_failed', user=username, error=e)
if mail[mail.find('@') + 1:] not in domains: if mail[mail.find('@') + 1:] not in domains:
raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:]) raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:])
if mail in aliases: if mail in aliases:
@ -351,7 +365,10 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail=
if not isinstance(add_mailalias, list): if not isinstance(add_mailalias, list):
add_mailalias = [add_mailalias] add_mailalias = [add_mailalias]
for mail in add_mailalias: for mail in add_mailalias:
try:
ldap.validate_uniqueness({'mail': mail}) ldap.validate_uniqueness({'mail': mail})
except Exception as e:
raise YunohostError('user_update_failed', user=username, error=e)
if mail[mail.find('@') + 1:] not in domains: if mail[mail.find('@') + 1:] not in domains:
raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:]) raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:])
user['mail'].append(mail) user['mail'].append(mail)
@ -391,12 +408,14 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail=
operation_logger.start() operation_logger.start()
if ldap.update('uid=%s,ou=users' % username, new_attr_dict): try:
ldap.update('uid=%s,ou=users' % username, new_attr_dict)
except Exception as e:
raise YunohostError('user_update_failed', user=username, error=e)
logger.success(m18n.n('user_updated')) logger.success(m18n.n('user_updated'))
app_ssowatconf() app_ssowatconf()
return user_info(username) return user_info(username)
else:
raise YunohostError('user_update_failed')
def user_info(username): def user_info(username):
@ -453,7 +472,7 @@ def user_info(username):
if service_status("dovecot")["status"] != "running": if service_status("dovecot")["status"] != "running":
logger.warning(m18n.n('mailbox_used_space_dovecot_down')) logger.warning(m18n.n('mailbox_used_space_dovecot_down'))
elif not user_permission_list(app="mail", permission="main", username=username)['permissions']: elif username not in user_permission_list(full=True)["permissions"]["mail.main"]["corresponding_users"]:
logger.warning(m18n.n('mailbox_disabled', user=username)) logger.warning(m18n.n('mailbox_disabled', user=username))
else: else:
cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0]
@ -480,81 +499,59 @@ def user_info(username):
'use': storage_use 'use': storage_use
} }
if result:
return result_dict return result_dict
else:
raise YunohostError('user_info_failed')
# #
# Group subcategory # Group subcategory
# #
def user_group_list(fields=None): def user_group_list(short=False, full=False, include_primary_groups=True):
""" """
List users List users
Keyword argument: Keyword argument:
filter -- LDAP filter used to search short -- Only list the name of the groups without any additional info
offset -- Starting number for user fetching full -- List all the info available for each groups
limit -- Maximum number of user fetched include_primary_groups -- Include groups corresponding to users (which should always only contains this user)
fields -- fields to fetch This option is set to false by default in the action map because we don't want to have
these displayed when the user runs `yunohost user group list`, but internally we do want
to list them when called from other functions
""" """
from yunohost.utils.ldap import _get_ldap_interface
# Fetch relevant informations
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
group_attr = { groups_infos = ldap.search('ou=groups,dc=yunohost,dc=org',
'cn': 'groupname',
'member': 'members',
'permission': 'permission'
}
attrs = ['cn']
groups = {}
if fields:
keys = group_attr.keys()
for attr in fields:
if attr in keys:
attrs.append(attr)
else:
raise YunohostError('field_invalid', attr)
else:
attrs = ['cn', 'member']
result = ldap.search('ou=groups,dc=yunohost,dc=org',
'(objectclass=groupOfNamesYnh)', '(objectclass=groupOfNamesYnh)',
attrs) ["cn", "member", "permission"])
for group in result: # Parse / organize information to be outputed
# The group "admins" should be hidden for the user
if group_attr['cn'] == "admins": users = user_list()["users"]
groups = {}
for infos in groups_infos:
name = infos["cn"][0]
if not include_primary_groups and name in users:
continue continue
entry = {}
for attr, values in group.items():
if values:
if attr == "member":
entry[group_attr[attr]] = []
for v in values:
entry[group_attr[attr]].append(v.split("=")[1].split(",")[0])
elif attr == "permission":
entry[group_attr[attr]] = {}
for v in values:
permission = v.split("=")[1].split(",")[0].split(".")[1]
pType = v.split("=")[1].split(",")[0].split(".")[0]
if permission in entry[group_attr[attr]]:
entry[group_attr[attr]][permission].append(pType)
else:
entry[group_attr[attr]][permission] = [pType]
else:
entry[group_attr[attr]] = values[0]
groupname = entry[group_attr['cn']] groups[name] = {}
groups[groupname] = entry
groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])]
if full:
groups[name]["permissions"] = [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])]
if short:
groups = groups.keys()
return {'groups': groups} return {'groups': groups}
@is_unit_operation([('groupname', 'user')]) @is_unit_operation([('groupname', 'group')])
def user_group_add(operation_logger, groupname, gid=None, sync_perm=True): def user_group_create(operation_logger, groupname, gid=None, primary_group=False, sync_perm=True):
""" """
Create group Create group
@ -565,8 +562,6 @@ def user_group_add(operation_logger, groupname, gid=None, sync_perm=True):
from yunohost.permission import permission_sync_to_user from yunohost.permission import permission_sync_to_user
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
operation_logger.start()
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
# Validate uniqueness of groupname in LDAP # Validate uniqueness of groupname in LDAP
@ -574,12 +569,12 @@ def user_group_add(operation_logger, groupname, gid=None, sync_perm=True):
'cn': groupname 'cn': groupname
}, base_dn='ou=groups,dc=yunohost,dc=org') }, base_dn='ou=groups,dc=yunohost,dc=org')
if conflict: if conflict:
raise YunohostError('group_name_already_exist', name=groupname) raise YunohostError('group_already_exist', group=groupname)
# Validate uniqueness of groupname in system group # Validate uniqueness of groupname in system group
all_existing_groupnames = {x.gr_name for x in grp.getgrall()} all_existing_groupnames = {x.gr_name for x in grp.getgrall()}
if groupname in all_existing_groupnames: if groupname in all_existing_groupnames:
raise YunohostError('system_groupname_exists') raise YunohostError('group_already_exist_on_system', group=groupname)
if not gid: if not gid:
# Get random GID # Get random GID
@ -596,16 +591,30 @@ def user_group_add(operation_logger, groupname, gid=None, sync_perm=True):
'gidNumber': gid, 'gidNumber': gid,
} }
if ldap.add('cn=%s,ou=groups' % groupname, attr_dict): # Here we handle the creation of a primary group
logger.success(m18n.n('group_created', group=groupname)) # We want to initialize this group to contain the corresponding user
# (then we won't be able to add/remove any user in this group)
if primary_group:
attr_dict["member"] = ["uid=" + groupname + ",ou=users,dc=yunohost,dc=org"]
operation_logger.start()
try:
ldap.add('cn=%s,ou=groups' % groupname, attr_dict)
except Exception as e:
raise YunohostError('group_creation_failed', group=groupname, error=e)
if sync_perm: if sync_perm:
permission_sync_to_user() permission_sync_to_user()
if not primary_group:
logger.success(m18n.n('group_created', group=groupname))
else:
logger.debug(m18n.n('group_created', group=groupname))
return {'name': groupname} return {'name': groupname}
raise YunohostError('group_creation_failed', group=groupname)
@is_unit_operation([('groupname', 'group')])
@is_unit_operation([('groupname', 'user')])
def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
""" """
Delete user Delete user
@ -617,102 +626,104 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
from yunohost.permission import permission_sync_to_user from yunohost.permission import permission_sync_to_user
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
forbidden_groups = ["all_users", "admins"] + user_list(fields=['uid'])['users'].keys() existing_groups = user_group_list()['groups'].keys()
if not force and groupname in forbidden_groups: if groupname not in existing_groups:
raise YunohostError('group_deletion_not_allowed', group=groupname) raise YunohostError('group_unknown', group=groupname)
# Refuse to delete primary groups of a user (e.g. group 'sam' related to user 'sam')
# without the force option...
#
# We also can't delete "all_users" because that's a special group...
existing_users = user_list()['users'].keys()
undeletable_groups = existing_users + ["all_users", "admins"]
if groupname in undeletable_groups and not force:
raise YunohostError('group_cannot_be_deleted', group=groupname)
operation_logger.start() operation_logger.start()
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
if not ldap.remove('cn=%s,ou=groups' % groupname): try:
raise YunohostError('group_deletion_failed', group=groupname) ldap.remove('cn=%s,ou=groups' % groupname)
except Exception as e:
raise YunohostError('group_deletion_failed', group=groupname, error=e)
logger.success(m18n.n('group_deleted', group=groupname))
if sync_perm: if sync_perm:
permission_sync_to_user() permission_sync_to_user()
if groupname not in existing_users:
logger.success(m18n.n('group_deleted', group=groupname))
else:
logger.debug(m18n.n('group_deleted', group=groupname))
@is_unit_operation([('groupname', 'user')])
def user_group_update(operation_logger, groupname, add_user=None, remove_user=None, force=False, sync_perm=True): @is_unit_operation([('groupname', 'group')])
def user_group_update(operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True):
""" """
Update user informations Update user informations
Keyword argument: Keyword argument:
groupname -- Groupname to update groupname -- Groupname to update
add_user -- User to add in group add -- User(s) to add in group
remove_user -- User to remove in group remove -- User(s) to remove in group
""" """
from yunohost.permission import permission_sync_to_user from yunohost.permission import permission_sync_to_user
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
if (groupname == 'all_users' or groupname == 'admins') and not force: # Refuse to edit a primary group of a user (e.g. group 'sam' related to user 'sam')
raise YunohostError('edit_group_not_allowed', group=groupname) # 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)
ldap = _get_ldap_interface() # We extract the uid for each member of the group to keep a simple flat list of members
current_group = user_group_info(groupname)["members"]
new_group = copy.copy(current_group)
# Populate group informations if add:
attrs_to_fetch = ['member'] users_to_add = [add] if not isinstance(add, list) else add
result = ldap.search(base='ou=groups,dc=yunohost,dc=org',
filter='cn=' + groupname, attrs=attrs_to_fetch)
if not result:
raise YunohostError('group_unknown', group=groupname)
group = result[0]
new_group_list = {'member': set(), 'memberUid': set()} for user in users_to_add:
if 'member' in group:
new_group_list['member'] = set(group['member'])
else:
group['member'] = []
existing_users = user_list(fields=['uid'])['users'].keys()
if add_user:
if not isinstance(add_user, list):
add_user = [add_user]
for user in add_user:
if user not in existing_users: if user not in existing_users:
raise YunohostError('user_unknown', user=user) raise YunohostError('user_unknown', user=user)
for user in add_user: if user in current_group:
userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" logger.warning(m18n.n('group_user_already_in_group', user=user, group=groupname))
if userDN in group['member']:
logger.warning(m18n.n('user_already_in_group', user=user, group=groupname))
new_group_list['member'].add(userDN)
if remove_user:
if not isinstance(remove_user, list):
remove_user = [remove_user]
for user in remove_user:
if user == groupname:
raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname)
for user in remove_user:
userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org"
if 'member' in group and userDN in group['member']:
new_group_list['member'].remove(userDN)
else: else:
logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) operation_logger.related_to.append(('user', user))
# Sychronise memberUid with member (to keep the posix group structure) new_group += users_to_add
# In posixgroup the main group of each user is only written in the gid number of the user
for member in new_group_list['member']:
member_Uid = member.split("=")[1].split(",")[0]
# Don't add main user in the group.
# Note that in the Unix system the main user of the group is linked by the gid in the user attribute.
# So the main user need to be not in the memberUid list of his own group.
if member_Uid != groupname:
new_group_list['memberUid'].add(member_Uid)
if remove:
users_to_remove = [remove] if not isinstance(remove, list) else remove
for user in users_to_remove:
if user not in current_group:
logger.warning(m18n.n('group_user_not_in_group', user=user, group=groupname))
else:
operation_logger.related_to.append(('user', user))
# Remove users_to_remove from new_group
# Kinda like a new_group -= users_to_remove
new_group = [u for u in new_group if u not in users_to_remove]
new_group_dns = ["uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group]
if set(new_group) != set(current_group):
operation_logger.start() operation_logger.start()
ldap = _get_ldap_interface()
try:
ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)})
except Exception as e:
raise YunohostError('group_update_failed', group=groupname, error=e)
if new_group_list['member'] != set(group['member']): if groupname != "all_users":
if not ldap.update('cn=%s,ou=groups' % groupname, new_group_list):
raise YunohostError('group_update_failed', group=groupname)
logger.success(m18n.n('group_updated', group=groupname)) logger.success(m18n.n('group_updated', group=groupname))
else:
logger.debug(m18n.n('group_updated', group=groupname))
if sync_perm: if sync_perm:
permission_sync_to_user() permission_sync_to_user()
return user_group_info(groupname) return user_group_info(groupname)
@ -727,59 +738,46 @@ def user_group_info(groupname):
""" """
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
group_attrs = [ # Fetch info for this group
'cn', 'member', 'permission' result = ldap.search('ou=groups,dc=yunohost,dc=org',
] "cn=" + groupname,
result = ldap.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs) ["cn", "member", "permission"])
if not result: if not result:
raise YunohostError('group_unknown', group=groupname) raise YunohostError('group_unknown', group=groupname)
group = result[0] infos = result[0]
result_dict = { # Format data
'groupname': group['cn'][0],
'member': None return {
'members': [_ldap_path_extract(p, "uid") for p in infos.get("member", [])],
'permissions': [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])]
} }
if 'member' in group:
result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']}
return result_dict
# #
# Permission subcategory # Permission subcategory
# #
def user_permission_list(app=None, permission=None, username=None, group=None, sync_perm=True): def user_permission_list(short=False, full=False):
import yunohost.permission import yunohost.permission
return yunohost.permission.user_permission_list(app, permission, username, group) return yunohost.permission.user_permission_list(short, full)
@is_unit_operation([('app', 'user')]) def user_permission_update(permission, add=None, remove=None, sync_perm=True):
def user_permission_add(operation_logger, app, permission="main", username=None, group=None, sync_perm=True):
import yunohost.permission import yunohost.permission
return yunohost.permission.user_permission_update(operation_logger, app, permission=permission, return yunohost.permission.user_permission_update(permission,
add_username=username, add_group=group, add=add, remove=remove,
del_username=None, del_group=None,
sync_perm=sync_perm) sync_perm=sync_perm)
@is_unit_operation([('app', 'user')]) def user_permission_reset(permission, sync_perm=True):
def user_permission_remove(operation_logger, app, permission="main", username=None, group=None, sync_perm=True):
import yunohost.permission import yunohost.permission
return yunohost.permission.user_permission_update(operation_logger, app, permission=permission, return yunohost.permission.user_permission_reset(permission,
add_username=None, add_group=None,
del_username=username, del_group=group,
sync_perm=sync_perm)
@is_unit_operation([('app', 'user')])
def user_permission_clear(operation_logger, app, permission=None, sync_perm=True):
import yunohost.permission
return yunohost.permission.user_permission_clear(operation_logger, app, permission,
sync_perm=sync_perm) sync_perm=sync_perm)

View file

@ -40,6 +40,20 @@ def _get_ldap_interface():
return _ldap_interface return _ldap_interface
# We regularly want to extract stuff like 'bar' in ldap path like
# foo=bar,dn=users.example.org,ou=example.org,dc=org so this small helper allow
# to do this without relying of dozens of mysterious string.split()[0]
#
# e.g. using _ldap_path_extract(path, "foo") on the previous example will
# return bar
def _ldap_path_extract(path, info):
for element in path.split(","):
if element.startswith(info + "="):
return element[len(info + "="):]
# Add this to properly close / delete the ldap interface / authenticator # Add this to properly close / delete the ldap interface / authenticator
# when Python exits ... # when Python exits ...
# Otherwise there's a risk that some funky error appears at the very end # Otherwise there's a risk that some funky error appears at the very end